[hamradio-commits] [direwolf] 01/03: Imported Upstream version 1.0
Iain Learmonth
irl-guest at moszumanska.debian.org
Sat Oct 11 18:03:51 UTC 2014
This is an automated email from the git hooks/post-receive script.
irl-guest pushed a commit to annotated tag debian/1.0-1
in repository direwolf.
commit c647d489ea30ed6eaecf3d7a17d059f2ea39882c
Author: Iain R. Learmonth <irl at fsfe.org>
Date: Sat Oct 11 18:06:39 2014 +0100
Imported Upstream version 1.0
---
APRStt-Implementation-Notes.pdf | Bin 0 -> 1034778 bytes
CHANGES.txt | 167 ++
LICENSE-dire-wolf.txt | 281 +++
LICENSE-other.txt | 5 +
Makefile.linux | 289 +++
Makefile.win | 326 +++
Quick-Start-Guide-Windows.pdf | Bin 0 -> 1081718 bytes
Raspberry-Pi-APRS.pdf | Bin 0 -> 835647 bytes
User-Guide.pdf | Bin 0 -> 2728705 bytes
aclients.c | 825 ++++++++
aprs_tt.c | 1436 +++++++++++++
aprs_tt.h | 100 +
atest.c | 447 ++++
audio.c | 1307 ++++++++++++
audio.h | 208 ++
audio_win.c | 1044 ++++++++++
ax25_pad.c | 1722 ++++++++++++++++
ax25_pad.h | 298 +++
beacon.c | 681 ++++++
beacon.h | 4 +
config.c | 2462 ++++++++++++++++++++++
config.h | 130 ++
decode_aprs.c | 3948 +++++++++++++++++++++++++++++++++++
decode_aprs.h | 5 +
dedupe.c | 243 +++
dedupe.h | 10 +
demod.c | 570 +++++
demod.h | 16 +
demod_9600.c | 463 +++++
demod_9600.h | 21 +
demod_afsk.c | 977 +++++++++
demod_afsk.h | 8 +
digipeater.c | 772 +++++++
digipeater.h | 62 +
direwolf.c | 885 ++++++++
direwolf.conf | 592 ++++++
direwolf.desktop | 10 +
direwolf.h | 39 +
dsp.c | 248 +++
dsp.h | 10 +
dtmf.c | 411 ++++
dtmf.h | 10 +
dw-icon.ico | Bin 0 -> 370070 bytes
dw-icon.png | Bin 0 -> 24142 bytes
dw-icon.rc | 1 +
dw-start.sh | 77 +
dwgps.c | 327 +++
dwgps.h | 15 +
encode_aprs.c | 797 +++++++
encode_aprs.h | 16 +
fcs_calc.c | 108 +
fcs_calc.h | 11 +
fsk_demod_agc.h | 2 +
fsk_demod_state.h | 172 ++
fsk_filters.h | 7 +
fsk_gen_filter.h | 15 +
gen_packets.c | 729 +++++++
gen_tone.c | 334 +++
gen_tone.h | 16 +
hdlc_rec.c | 513 +++++
hdlc_rec.h | 24 +
hdlc_rec2.c | 661 ++++++
hdlc_rec2.h | 35 +
hdlc_send.c | 215 ++
hdlc_send.h | 10 +
igate.c | 1491 ++++++++++++++
igate.h | 65 +
kiss.c | 923 +++++++++
kiss.h | 21 +
kiss_frame.c | 407 ++++
kiss_frame.h | 47 +
kissnet.c | 671 ++++++
kissnet.h | 21 +
latlong.c | 281 +++
latlong.h | 13 +
ll2utm.c | 55 +
misc/README-dire-wolf.txt | 8 +
misc/strcasestr.c | 64 +
misc/strsep.c | 22 +
misc/strtok_r.c | 102 +
morse.c | 381 ++++
multi_modem.c | 484 +++++
multi_modem.h | 20 +
ptt.c | 690 +++++++
ptt.h | 23 +
pttest.c | 287 +++
rdq.c | 453 ++++
rdq.h | 28 +
redecode.c | 252 +++
redecode.h | 15 +
regex/COPYING | 340 +++
regex/COPYING.LIB | 510 +++++
regex/INSTALL | 463 +++++
regex/LICENSES | 219 ++
regex/NEWS | 0
regex/README | 0
regex/README-dire-wolf.txt | 6 +
regex/re_comp.h | 26 +
regex/regcomp.c | 3801 ++++++++++++++++++++++++++++++++++
regex/regex.c | 74 +
regex/regex.h | 580 ++++++
regex/regex_internal.c | 1717 ++++++++++++++++
regex/regex_internal.h | 769 +++++++
regex/regexec.c | 4333 +++++++++++++++++++++++++++++++++++++++
rrbb.c | 620 ++++++
rrbb.h | 102 +
server.c | 1249 +++++++++++
server.h | 19 +
symbols-new.txt | 409 ++++
symbols.c | 898 ++++++++
symbols.h | 14 +
symbolsX.txt | 364 ++++
textcolor.c | 345 ++++
textcolor.h | 53 +
tocalls.txt | 209 ++
tq.c | 599 ++++++
tq.h | 35 +
tt_text.c | 677 ++++++
tt_text.h | 17 +
tt_user.c | 806 ++++++++
tt_user.h | 10 +
tune.h | 1 +
udp_test.c | 407 ++++
utm/LatLong-UTMconversion.c | 190 ++
utm/LatLong-UTMconversion.h | 30 +
utm/README.txt | 10 +
utm/SwissGrid.cpp | 140 ++
utm/UTMConversions.cpp | 39 +
utm/constants.h | 41 +
utm2ll.c | 72 +
version.h | 7 +
xmit.c | 728 +++++++
xmit.h | 24 +
133 files changed, 52894 insertions(+)
diff --git a/APRStt-Implementation-Notes.pdf b/APRStt-Implementation-Notes.pdf
new file mode 100755
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 100755
index 0000000..e76dbb6
--- /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 100755
index 0000000..3a51aab
--- /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 100755
index 0000000..1027d22
--- /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 100755
index 0000000..5a2bfec
--- /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 100755
index 0000000..b879533
--- /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 100755
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 100755
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 100755
index 0000000..ff3b46f
Binary files /dev/null and b/User-Guide.pdf differ
diff --git a/aclients.c b/aclients.c
new file mode 100755
index 0000000..7f8b722
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <winsock2.h>
+// default is 0x0400
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0501 /* Minimum OS version is XP. */
+#include <ws2tcpip.h>
+#else
+//#define __USE_XOPEN2KXSI 1
+//#define __USE_XOPEN 1
+#include <stdlib.h>
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/errno.h>
+#endif
+
+#include <unistd.h>
+#include <stdio.h>
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+
+
+#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<num_clients; j++) {
+ char stemp[100];
+ char *p;
+
+ strcpy (stemp, argv[j+1]);
+ p = strtok (stemp, "=");
+ if (p == NULL) {
+ printf ("Internal error 1\n");
+ exit (1);
+ }
+ strcpy (hostname[j], "localhost");
+ strcpy (port[j], p);
+ p = strtok (NULL, "=");
+ if (p == NULL) {
+ printf ("Missing description after %s\n", port[j]);
+ exit (1);
+ }
+ strcpy (description[j], p);
+ }
+
+ //printf ("_WIN32_WINNT = %04x\n", _WIN32_WINNT);
+ //for (j=0; j<num_clients; j++) {
+ // printf ("%s,%s,%s\n", hostname[j], port[j], description[j]);
+ //}
+
+ memset (packets, ' ', (size_t)LINE_WIDTH);
+ packets[LINE_WIDTH] = '\0';
+
+ for (j=0; j<num_clients; j++) {
+ packet_count[j] = 0;
+ }
+
+
+ for (j=0; j<num_clients; j++) {
+#if __WIN32__
+ if (isdigit(port[j][0])) {
+ client_th[j] = (HANDLE)_beginthreadex (NULL, 0, client_thread_net, (void *)j, 0, NULL);
+ }
+ else {
+ client_th[j] = (HANDLE)_beginthreadex (NULL, 0, client_thread_serial, (void *)j, 0, NULL);
+ }
+ if (client_th[j] == NULL) {
+ printf ("Internal error: Could not create client thread %d.\n", j);
+ exit (1);
+ }
+#else
+ if (isdigit(port[j][0])) {
+ e = pthread_create (&client_tid[j], NULL, client_thread_net, (void *)(long)j);
+ }
+ else {
+ e = pthread_create (&client_tid[j], NULL, client_thread_serial, (void *)(long)j);
+ }
+ if (e != 0) {
+ perror("Internal error: Could not create client thread.");
+ exit (1);
+ }
+#endif
+ }
+
+ start_time = time(NULL);
+ next_print_time = start_time + (PRINT_MINUTES) * 60;
+
+/*
+ * Print results from clients.
+ */
+ while (1) {
+ int k;
+ int something;
+
+ SLEEP_MS(100);
+
+ something = 0;
+ for (k=0; k<LINE_WIDTH && ! something; k++) {
+ if (packets[k] != ' ') {
+ something = 1;
+ }
+ }
+ if (something) {
+ /* time for others to catch up. */
+ SLEEP_MS(200);
+
+ printf ("%s\n", packets);
+ memset (packets, ' ', (size_t)LINE_WIDTH);
+ }
+
+ now = time(NULL);
+ if (now >= next_print_time) {
+ next_print_time = now + (PRINT_MINUTES) * 60;
+
+ printf ("\nTotals after %d minutes", (int)((now - start_time) / 60));
+
+ for (j=0; j<num_clients; j++) {
+ printf (", %s %d", description[j], packet_count[j]);
+ }
+ printf ("\n\n");
+ }
+ }
+
+
+ return 0; // unreachable
+}
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name: client_thread_net
+ *
+ * Purpose: Establish connection with a TNC via network.
+ *
+ * Inputs: arg - My instance index, 0 thru MAX_CLIENTS-1.
+ *
+ * Outputs: packets - Received packets are put in the corresponding column.
+ *
+ *--------------------------------------------------------------------*/
+
+#define MAX_HOSTS 30
+
+#if __WIN32__
+static unsigned __stdcall client_thread_net (void *arg)
+#else
+static void * client_thread_net (void *arg)
+#endif
+{
+ int my_index;
+ struct addrinfo hints;
+ struct addrinfo *ai_head = NULL;
+ struct addrinfo *ai;
+ struct addrinfo *hosts[MAX_HOSTS];
+ int num_hosts, n;
+ int err;
+ char ipaddr_str[46]; /* text form of IP address */
+#if __WIN32__
+ WSADATA wsadata;
+#endif
+/*
+ * File descriptor for socket to server.
+ * Set to -1 if not connected.
+ * (Don't use SOCKET type because it is unsigned.)
+*/
+ int server_sock = -1;
+ struct agwpe_s mon_cmd;
+ char data[1024];
+ int use_chan = -1;
+
+
+ my_index = (int)(long)arg;
+
+#if DEBUGx
+ printf ("DEBUG: client_thread_net %d start, port = '%s'\n", my_index, port[my_index]);
+#endif
+
+#if __WIN32__
+ err = WSAStartup (MAKEWORD(2,2), &wsadata);
+ if (err != 0) {
+ printf("WSAStartup failed: %d\n", err);
+ return (0);
+ }
+
+ if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
+ printf("Could not find a usable version of Winsock.dll\n");
+ WSACleanup();
+ //sleep (1);
+ return (0);
+ }
+#endif
+
+ memset (&hints, 0, sizeof(hints));
+
+ hints.ai_family = AF_UNSPEC; /* Allow either IPv4 or IPv6. */
+ // hints.ai_family = AF_INET; /* IPv4 only. */
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+/*
+ * Connect to TNC server.
+ */
+
+ ai_head = NULL;
+ err = getaddrinfo(hostname[my_index], port[my_index], &hints, &ai_head);
+ if (err != 0) {
+#if __WIN32__
+ printf ("Can't get address for server %s, err=%d\n",
+ hostname[my_index], WSAGetLastError());
+#else
+ printf ("Can't get address for server %s, %s\n",
+ hostname[my_index], gai_strerror(err));
+#endif
+ freeaddrinfo(ai_head);
+ exit (1);
+ }
+
+#if DEBUG_DNS
+ printf ("getaddrinfo returns:\n");
+#endif
+ num_hosts = 0;
+ for (ai = ai_head; ai != NULL; ai = ai->ai_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; n<num_hosts; n++) {
+ ia_to_text (hosts[n]->ai_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; n<num_hosts; n++) {
+ int is;
+
+ ai = hosts[n];
+
+ ia_to_text (ai->ai_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: <<UI>>:
+ * `c0+!h4>/]"4a}146.520MHz Listening, V-Alert & WLNK-1=
+ *
+ * N8VIM>BEACON,W1XM,WB2OSZ-1,WIDE2*: <UI>:
+ * !4240.85N/07133.99W_PHG72604/ Pepperell, MA. WX. 442.9+ PL100
+ *
+ * Don't know why some are <<UI>> and some <UI>.
+ *
+ * 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 100755
index 0000000..1b4a791
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+/*------------------------------------------------------------------
+ *
+ * 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 <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+
+
+#include <ctype.h>
+#include <assert.h>
+
+#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<MAX_CHANS; c++) {
+ msg_len[c] = 0;
+ msg_str[c][0] = '\0';
+ }
+
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: aprs_tt_button
+ *
+ * Purpose: Process one received button press.
+ *
+ * Inputs: chan - Audio channel it came from.
+ *
+ * button 0123456789ABCD*# - Received button press.
+ * $ - No activity timeout.
+ * space - Quiet time filler.
+ *
+ * Returns: None
+ *
+ * Description: Individual key presses are accumulated here until
+ * the # message terminator is found.
+ * The complete message is then processed.
+ * The touch tone decoder sends $ if no activity
+ * for some amount of time, perhaps 5 seconds.
+ * A partially accumulated messge is discarded if
+ * there is a long gap.
+ *
+ * '.' means no activity during processing period.
+ * space, between blocks, shouldn't get here.
+ *
+ *----------------------------------------------------------------*/
+
+#ifndef TT_MAIN
+
+void aprs_tt_button (int chan, char button)
+{
+ static int poll_period = 0;
+
+ assert (chan >= 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<len; i++) {
+ if (isdigit(str[i])) {
+ sum += str[i] - '0';
+ }
+ else if (str[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<tt_config.ttloc_len; ipat++) {
+
+ len = strlen(tt_config.ttloc_ptr[ipat].pattern);
+
+ if (strlen(e) == len) {
+
+ match = 1;
+ strcpy (xstr, "");
+ strcpy (ystr, "");
+ strcpy (zstr, "");
+ strcpy (bstr, "");
+ strcpy (dstr, "");
+
+ for (k=0; k<len; k++) {
+ mc = tt_config.ttloc_ptr[ipat].pattern[k];
+ switch (mc) {
+
+ case 'B':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (e[k] != mc) {
+ match = 0;
+ }
+ break;
+
+ case 'x':
+ if (isdigit(e[k])) {
+ char stemp[2];
+ stemp[0] = e[k];
+ stemp[1] = '\0';
+ strcat (xstr, stemp);
+ }
+ else {
+ match = 0;
+ }
+ break;
+
+ case 'y':
+ if (isdigit(e[k])) {
+ char stemp[2];
+ stemp[0] = e[k];
+ stemp[1] = '\0';
+ strcat (ystr, stemp);
+ }
+ else {
+ match = 0;
+ }
+ break;
+
+ case 'z':
+ if (isdigit(e[k])) {
+ char stemp[2];
+ stemp[0] = e[k];
+ stemp[1] = '\0';
+ strcat (zstr, stemp);
+ }
+ else {
+ match = 0;
+ }
+ break;
+
+ case 'b':
+ if (isdigit(e[k])) {
+ char stemp[2];
+ stemp[0] = e[k];
+ stemp[1] = '\0';
+ strcat (bstr, stemp);
+ }
+ else {
+ match = 0;
+ }
+ break;
+
+ case 'd':
+ if (isdigit(e[k])) {
+ char stemp[2];
+ stemp[0] = e[k];
+ stemp[1] = '\0';
+ strcat (dstr, stemp);
+ }
+ else {
+ match = 0;
+ }
+ break;
+
+ default:
+ dw_printf ("find_ttloc_match: shouldn't be here.\n");
+ /* Shouldn't be here. */
+ match = 0;
+ break;
+
+ } /* switch */
+ } /* for k */
+
+ if (match) {
+ return (ipat);
+ }
+ } /* if strlen */
+ }
+ return (-1);
+
+} /* end find_ttloc_match */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: parse_comment
+ *
+ * Purpose: Extract comment / status or other special information from touch tone message.
+ *
+ * Inputs: e - An "entry" extracted from a complete
+ * APRStt messsage.
+ * In this case, it should start with "C".
+ *
+ * Outputs: m_comment
+ * m_mic_e
+ *
+ * Returns: 0 for success or one of the TT_ERROR_... codes.
+ *
+ * Description: We recognize these different formats:
+ *
+ * Cn - One digit for MIC-E position comment.
+ * Always / plus exactly 10 characters.
+ *
+ * Cnnnnnn - Six digit frequency reformatted as nnn.nnnMHz
+ *
+ * Cttt...tttt - General comment in Multi-press encoding.
+ *
+ *----------------------------------------------------------------*/
+
+static int parse_comment (char *e)
+{
+ int len;
+ int n;
+
+ assert (*e == 'C');
+
+ len = strlen(e);
+
+ if (len == 2 && isdigit(e[1])) {
+ m_mic_e = e[1];
+ return (0);
+ }
+
+ if (len == 7 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3]) && isdigit(e[4]) && isdigit(e[5]) && isdigit(e[6])) {
+ m_freq[0] = e[1];
+ m_freq[1] = e[2];
+ m_freq[2] = e[3];
+ m_freq[3] = '.';
+ m_freq[4] = e[4];
+ m_freq[5] = e[5];
+ m_freq[6] = e[6];
+ m_freq[7] = 'M';
+ m_freq[8] = 'H';
+ m_freq[9] = 'z';
+ m_freq[10] = '\0';
+ return (0);
+ }
+
+ tt_multipress_to_text (e+1, 0, m_comment);
+ return (0);
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: raw_tt_data_to_app
+ *
+ * Purpose: Send raw touch tone data to application.
+ *
+ * Inputs: chan - Channel where touch tone data heard.
+ * msg - String of button pushes.
+ * Normally ends with #.
+ *
+ * Global In: m_callsign
+ * m_symtab_or_overlay
+ * m_symbol_code
+ *
+ * Returns: None
+ *
+ * Description:
+ * Put raw touch tone message in a packet and send to application.
+ * The APRS protocol does not have provision for this.
+ * For now, use the unused "t" message type.
+ * TODO: Get an officially sanctioned method.
+ *
+ * Use callsign for source if available.
+ * Encode the overlay in the destination.
+ *
+ *----------------------------------------------------------------*/
+
+
+static void raw_tt_data_to_app (int chan, char *msg)
+{
+
+#if TT_MAIN
+ return ;
+#else
+ char src[10], dest[10];
+ char raw_tt_msg[200];
+ packet_t pp;
+ char *c, *s;
+ int i;
+ int err;
+
+/*
+ * "callsign" could be a general object name up to
+ * 9 characters and a space which will get ax25_from_text upset.
+ */
+
+ strcpy (src, "");
+ if (strlen(m_callsign) > 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 100755
index 0000000..9826e5f
--- /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 100755
index 0000000..4fbea80
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*-------------------------------------------------------------------
+ *
+ * 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 <stdio.h>
+#include <unistd.h>
+//#include <fcntl.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <time.h>
+#include <getopt.h>
+
+
+#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<MAX_CHANS; channel++) {
+
+ 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[channel]);
+
+ modem.num_freq[channel] = 1;
+ modem.offset[channel] = 0;
+// temp test
+ //modem.num_subchan[channel] = modem.num_freq[channel] = 3;
+ //modem.num_subchan[channel] = modem.num_freq[channel] = 5;
+ //modem.offset[channel] = 100;
+
+ //strcpy (modem.ptt_device[channel], "");
+ //modem.ptt_line[channel] = PTT_NONE;
+
+ //modem.slottime[channel] = DEFAULT_SLOTTIME;
+ //modem.persist[channel] = DEFAULT_PERSIST;
+ //modem.txdelay[channel] = DEFAULT_TXDELAY;
+ //modem.txtail[channel] = DEFAULT_TXTAIL;
+ }
+
+ 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, "B:P:D:",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+
+ 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);
+
+ printf ("Data rate set to %d bits / second.\n", modem.baud[0]);
+
+ if (modem.baud[0] < 100 || modem.baud[0] > 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<modem.num_channels; c++)
+ {
+
+ /* 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;
+
+#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 100755
index 0000000..7e52f64
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+
+#if USE_ALSA
+#include <alsa/asoundlib.h>
+#else
+#include <sys/soundcard.h>
+#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<MAX_CHANS; chan++) {
+ if (pa -> 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 100755
index 0000000..a6eb535
--- /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 100755
index 0000000..fcd7a53
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <ctype.h>
+#include <io.h>
+#include <fcntl.h>
+
+#include <windows.h>
+#include <mmsystem.h>
+
+#ifndef WAVE_FORMAT_96M16
+#define WAVE_FORMAT_96M16 0x40000
+#define WAVE_FORMAT_96S16 0x80000
+#endif
+
+#include <winsock2.h>
+#define _WIN32_WINNT 0x0501
+#include <ws2tcpip.h>
+
+
+#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<MAX_CHANS; chan++) {
+ if (pa -> 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 ; n<num_devices && in_dev_no == WAVE_MAPPER ; n++) {
+ if ( ! waveInGetDevCaps(n, &wic, sizeof(WAVEINCAPS))) {
+ if (strstr(wic.szPname, pa->adevice_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 ; n<num_devices && out_dev_no == WAVE_MAPPER ; n++) {
+ if ( ! waveOutGetDevCaps(n, &woc, sizeof(WAVEOUTCAPS))) {
+ if (strstr(woc.szPname, pa->adevice_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; n<num_devices; n++) {
+
+ if ( ! waveInGetDevCaps(n, &wic, sizeof(WAVEINCAPS))) {
+ dw_printf ("%c %d: %s\n", n==in_dev_no ? '*' : ' ', n, wic.szPname);
+ //dw_printf ("%c %d: %-31s\n", n==in_dev_no ? '*' : ' ', n, wic.szPname);
+ //print_capabilities (wic.dwFormats);
+ //dw_printf ("\n");
+ }
+ }
+
+ /* Display stdin or udp:port if appropriate. */
+
+ if (audio_in_type != AUDIO_IN_TYPE_SOUNDCARD) {
+ dw_printf ("* %s\n", pa->adevice_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; n<num_devices; n++) {
+
+ if ( ! waveOutGetDevCaps(n, &woc, sizeof(WAVEOUTCAPS))) {
+ dw_printf ("%c %d: %s\n", n==out_dev_no ? '*' : ' ', n, woc.szPname);
+ //dw_printf ("%c %d: %-31s\n", n==out_dev_no ? '*' : ' ', n, woc.szPname);
+ //print_capabilities (woc.dwFormats);
+ //dw_printf ("\n");
+ }
+ }
+
+ err = waveOutOpen (&audio_out_handle, out_dev_no, &wf, (DWORD_PTR)out_callback, 0, CALLBACK_FUNCTION);
+ if (err != MMSYSERR_NOERROR) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Could not open audio device for output.\n");
+ return (-1);
+ }
+
+/*
+ * Set up the output buffers.
+ * We use dwUser to indicate it is available for filling.
+ */
+
+ memset ((void*)out_wavehdr, 0, sizeof(out_wavehdr));
+
+ for (n = 0; n < NUM_OUT_BUF; n++) {
+ out_wavehdr[n].lpData = malloc(outbuf_size);
+ out_wavehdr[n].dwUser = DWU_FILLING;
+ out_wavehdr[n].dwBufferLength = 0;
+ }
+ out_current = 0;
+
+
+/*
+ * Open audio input device.
+ */
+
+ switch (audio_in_type) {
+
+/*
+ * Soundcard.
+ */
+ case AUDIO_IN_TYPE_SOUNDCARD:
+
+ InitializeCriticalSection (&in_cs);
+
+ err = waveInOpen (&audio_in_handle, in_dev_no, &wf, (DWORD_PTR)in_callback, 0, CALLBACK_FUNCTION);
+ if (err != MMSYSERR_NOERROR) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Could not open audio device for input.\n");
+ return (-1);
+ }
+
+
+ /*
+ * Set up the input buffers.
+ */
+
+ memset ((void*)in_wavehdr, 0, sizeof(in_wavehdr));
+
+ for (n = 0; n < NUM_OUT_BUF; n++) {
+ in_wavehdr[n].dwBufferLength = outbuf_size; /* all the same size */
+ in_wavehdr[n].lpData = malloc(outbuf_size);
+ }
+ in_headp = NULL;
+
+ /*
+ * Give them to the sound input system.
+ */
+
+ for (n = 0; n < NUM_OUT_BUF; n++) {
+ waveInPrepareHeader(audio_in_handle, &(in_wavehdr[n]), sizeof(WAVEHDR));
+ waveInAddBuffer(audio_in_handle, &(in_wavehdr[n]), sizeof(WAVEHDR));
+ }
+
+ /*
+ * Start it up.
+ * The callback function is called when one is filled.
+ */
+
+ waveInStart (audio_in_handle);
+ break;
+
+/*
+ * UDP.
+ */
+ case AUDIO_IN_TYPE_SDR_UDP:
+
+ {
+ WSADATA wsadata;
+ struct sockaddr_in si_me;
+ //int slen=sizeof(si_me);
+ //int data_size = 0;
+ int err;
+
+ err = WSAStartup (MAKEWORD(2,2), &wsadata);
+ if (err != 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("WSAStartup failed: %d\n", err);
+ return (-1);
+ }
+
+ if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("Could not find a usable version of Winsock.dll\n");
+ WSACleanup();
+ return (-1);
+ }
+
+ // Create UDP Socket
+
+ udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (udp_sock == INVALID_SOCKET) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Couldn't create socket, errno %d\n", WSAGetLastError());
+ return -1;
+ }
+
+ memset((char *) &si_me, 0, sizeof(si_me));
+ si_me.sin_family = AF_INET;
+ si_me.sin_port = htons((short)atoi(pa->adevice_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 100755
index 0000000..1bd3451
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+#include <ctype.h>
+#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; i<this_p->num_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; j<this_p->num_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
+ * <DEL>, 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 100755
index 0000000..347a394
--- /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 100755
index 0000000..085dcc8
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module: beacon.c
+ *
+ * Purpose: Transmit messages on a fixed schedule.
+ *
+ * Description: Transmit periodic messages as specified in the config file.
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <math.h>
+
+#include <time.h>
+#if __WIN32__
+#include <windows.h>
+#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; j<g_misc_config_p->num_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; j<g_misc_config_p->num_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; j<g_misc_config_p->num_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; j<g_misc_config_p->num_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; j<g_misc_config_p->num_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; j<g_misc_config_p->num_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; j<g_misc_config_p->num_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 100755
index 0000000..97f3dd5
--- /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 100755
index 0000000..d3f7eda
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+// #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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <ctype.h>
+
+#if __WIN32__
+#include "pthreads/pthread.h"
+#else
+#include <pthread.h>
+#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; channel<MAX_CHANS; channel++) {
+
+ p_modem->modem_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; j<strlen(t); j++) {
+ if ( ! isdigit(t[j])) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: TTPOINT pattern must be B and digits only.\n", line);
+ }
+ }
+
+ /* Latitude */
+
+ t = strtok (NULL, " ,\t\n\r");
+ if (t == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Missing latitude for TTPOINT command.\n", line);
+ continue;
+ }
+ tl->point.lat = parse_ll(t,LAT,line);
+
+ /* Longitude */
+
+ t = 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; j<p_tt_config->ttloc_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; j<strlen(t); j++) {
+ if ( ! isdigit(t[j]) && t[j] != 'b' && t[j] != 'd') {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: TTVECTOR pattern must contain only B, digits, b, and d.\n", line);
+ }
+ }
+
+ /* Latitude */
+
+ t = strtok (NULL, " ,\t\n\r");
+ if (t == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Missing latitude for TTVECTOR command.\n", line);
+ continue;
+ }
+ tl->vector.lat = parse_ll(t,LAT,line);
+
+ /* Longitude */
+
+ t = 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; j<NUM_UNITS && meters == 0; j++) {
+ if (strcasecmp(units[j].name, t) == 0) {
+ meters = units[j].meters;
+ }
+ }
+ if (meters == 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Unrecognized unit for TTVECTOR command. Using miles.\n", line);
+ meters = 1609.344;
+ }
+ tl->vector.scale = scale * meters;
+
+ //dw_printf ("ttvector: %f meters\n", tl->vector.scale);
+
+ /* temp debugging */
+
+ //for (j=0; j<p_tt_config->ttloc_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; j<strlen(t); j++) {
+ if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y') {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: TTGRID pattern must be B, optional digit, xxx, yyy.\n", line);
+ }
+ }
+
+ /* Minimum Latitude - all zeros in received data */
+
+ t = 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.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; j<p_tt_config->ttloc_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; j<strlen(t); j++) {
+ if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y') {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: TTUTM pattern must be B, optional digit, xxx, yyy.\n", line);
+ }
+ }
+
+ /* Zone 1 - 60 and optional latitudinal letter. */
+
+ t = strtok (NULL, " ,\t\n\r");
+ if (t == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Missing zone for TTUTM command.\n", line);
+ continue;
+ }
+ memset (tl->utm.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<strlen(t); j++) {
+ if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y' && t[j] != 'z') {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: TTMACRO pattern can contain only digits and lower case x, y, or z.\n", line);
+ continue;
+ }
+ if (t[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<strlen(t); j++) {
+ if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y' && t[j] != 'z' &&
+ t[j] != 'A' && t[j] != 'B' && t[j] != 'C' && t[j] != 'D' &&
+ t[j] != '*' && t[j] != '#') {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: TTMACRO definition can contain only 0-9, A, B, C, D, *, #, x, y, z.\n", line);
+ continue;
+ }
+ if (t[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; i<p_digi_config->num_chans; i++) {
+ for (j=0; j<p_digi_config->num_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; k<p_misc_config->num_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 100755
index 0000000..8ac91b6
--- /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 100755
index 0000000..089ba8b
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdio.h>
+#include <time.h>
+#include <assert.h>
+#include <stdlib.h> /* for atof */
+#include <string.h> /* for strtok */
+#if __WIN32__
+char *strsep(char **stringp, const char *delim);
+#endif
+#include <math.h> /* for pow */
+#include <ctype.h> /* for isdigit */
+#include <fcntl.h>
+
+#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; j<n; j++) {
+ if ((unsigned)g_comment[j] == (char)0xb0 && (j == 0 || ! (g_comment[j-1] & 0x80))) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("Character code 0xb0 is probably an attempt at a degree symbol.\n");
+ dw_printf("The correct encoding is 0xc2 0xb0 in UTF-8.\n");
+ }
+ }
+ for (j=0; j<n; j++) {
+ if ((unsigned)g_comment[j] == (char)0xf8 && (j == n-1 || (g_comment[j+1] & 0xc0) != 0xc0)) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("Character code 0xf8 is probably an attempt at a degree symbol.\n");
+ dw_printf("The correct encoding is 0xc2 0xb0 in UTF-8.\n");
+ }
+ }
+ }
+}
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function: aprs_ll_pos
+ *
+ * Purpose: Decode "Lat/Long Position Report - without Timestamp"
+ *
+ * Reports without a timestamp can be regarded as real-time.
+ *
+ * Inputs: info - Pointer to Information field.
+ * ilen - Information field length.
+ *
+ * Outputs: 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: !4309.95NS07307.13W#PHG3320 W2,NY2 Mt Equinox VT k2lm at arrl.net
+ * !4237.14NS07120.83W#
+ * =4246.40N/07115.15W# {UIV32}
+ *
+ * TODO: (?) Special case, DF report when sym table id = '/' and symbol code = '\'.
+ *
+ * =4903.50N/07201.75W\088/036/270/729
+ *
+ *------------------------------------------------------------------*/
+
+static void aprs_ll_pos (unsigned char *info, int ilen)
+{
+
+ struct aprs_ll_pos_s {
+ char dti; /* ! or = */
+ position_t pos;
+ char comment[43]; /* Start of comment could be data extension(s). */
+ } *p;
+
+ struct aprs_compressed_pos_s {
+ char dti; /* ! or = */
+ compressed_position_t cpos;
+ char comment[40]; /* No data extension allowed for compressed location. */
+ } *q;
+
+
+ strcpy (g_msg_type, "Position");
+
+ p = (struct aprs_ll_pos_s *)info;
+ q = (struct aprs_compressed_pos_s *)info;
+
+ if (isdigit((unsigned char)(p->pos.lat[0]))) /* Human-readable location. */
+ {
+ decode_position (&(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 <Knox,TN> clintserman at 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 <Knox,TN> clintserman at 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 <Knox,TN> clintserman at 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 <Knox,TN> clintserman at 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*<ONV0 446325-146IDLE<CR>
+ *
+ *------------------------------------------------------------------*/
+
+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: <IGATE,MSG_CNT=3,LOC_CNT=49<CR>
+ *
+ * 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<CR><LF>
+ *
+ * The other begins with !! and is called "logging mode." Example:
+ *
+ * !!000000A600B50000----------------001C01D500000017<CR><LF>
+ *
+ *
+ * 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<num_tocalls; n++) {
+ if (strncmp(dest, tocalls[n].prefix, tocalls[n].len) == 0) {
+ strncpy (g_mfr, tocalls[n].description, sizeof(g_mfr)-1);
+ g_mfr[sizeof(g_mfr)-1] = '\0';
+ return;
+ }
+ }
+
+} /* end decode_tocall */
+
+
+/*------------------------------------------------------------------
+ *
+ * Function: process_comment
+ *
+ * Purpose: Extract optional items from the comment.
+ *
+ * Inputs: pstart - Pointer to start of left over information field.
+ *
+ * clen - Length of comment or -1 to take it all.
+ *
+ * Outputs: g_comment
+ *
+ * Description: After processing fixed and possible optional parts
+ * of the message, everything left over is a comment.
+ *
+ * Except!!!
+ *
+ * There are could be some other pieces of data, with
+ * particular formats, buried in there.
+ * Pull out those special items and put everything
+ * else into g_comment.
+ *
+ * References: http://www.aprs.org/info/freqspec.txt
+ *
+ * 999.999MHz T100 +060 Voice frequency.
+ *
+ * http://www.aprs.org/datum.txt
+ *
+ * !DAO! APRS precision and Datum option.
+ *
+ * Protocol reference, end of chaper 6.
+ *
+ * /A=123456 Altitude
+ *
+ *
+ *------------------------------------------------------------------*/
+
+#define sign(x) (((x)>=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 100755
index 0000000..68bb3ae
--- /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 100755
index 0000000..396e279
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+#include <time.h>
+
+
+#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<HISTORY_MAX; j++) {
+ if (history[j].time_stamp >= 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 100755
index 0000000..abac2ae
--- /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 100755
index 0000000..695dcab
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+// #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 <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+
+#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<MAX_CHANS; chan++)
+ {
+ for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) {
+ struct demodulator_state_s *D;
+
+ assert (subchan >= 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<UPSAMPLE; k++) {
+ demod_9600_process_sample (chan, 0, D);
+ }
+ demod_9600_process_sample (chan, sam*UPSAMPLE, D);
+#else
+ /* Linear interpolation. */
+ static int prev_sam;
+ switch (UPSAMPLE) {
+ case 1:
+ demod_9600_process_sample (chan, sam);
+
+ break;
+ case 2:
+ demod_9600_process_sample (chan, (prev_sam + sam) / 2, D);
+ demod_9600_process_sample (chan, sam, D);
+ break;
+ case 3:
+ demod_9600_process_sample (chan, (2 * prev_sam + sam) / 3, D);
+ demod_9600_process_sample (chan, (prev_sam + 2 * sam) / 3, D);
+ demod_9600_process_sample (chan, sam, D);
+ break;
+ case 4:
+ demod_9600_process_sample (chan, (3 * prev_sam + sam) / 4, D);
+ demod_9600_process_sample (chan, (prev_sam + sam) / 2, D);
+ demod_9600_process_sample (chan, (prev_sam + 3 * sam) / 4, D);
+ demod_9600_process_sample (chan, sam, D);
+ break;
+ default:
+ assert (0);
+ break;
+ }
+ prev_sam = sam;
+#endif
+ break;
+ }
+ return;
+
+} /* end demod_process_sample */
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * 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 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 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 100755
index 0000000..d1af040
--- /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 100755
index 0000000..cdf3554
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+// #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 <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+
+#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<filter_size; j++) {
+ sum += filter[j] * data[j];
+ }
+ return (sum);
+}
+
+/* Automatic gain control. */
+/* Result should settle down to 1 unit peak to peak. i.e. -0.5 to +0.5 */
+
+__attribute__((hot))
+static inline float agc (float in, float fast_attack, float slow_decay, float *ppeak, float *pvalley)
+{
+ if (in >= *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 100755
index 0000000..486e597
--- /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 100755
index 0000000..fb7e8f2
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+// #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 <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+
+#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<filter_size; j++) {
+ sum += filter[j] * data[j];
+ }
+ return (sum);
+}
+
+/* Automatic gain control. */
+/* Result should settle down to 1 unit peak to peak. i.e. -0.5 to +0.5 */
+
+__attribute__((hot))
+static inline float agc (float in, float fast_attack, float slow_decay, float *ppeak, float *pvalley)
+{
+ if (in >= *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; j<D->ms_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; j<D->ms_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; j<D->ms_filter_size; j++) {
+ dc_bias += D->m_sin_table[j];
+ }
+ for (j=0; j<D->ms_filter_size; j++) {
+ D->m_sin_table[j] -= dc_bias / D->ms_filter_size;
+ }
+
+ dc_bias = 0;
+ for (j=0; j<D->ms_filter_size; j++) {
+ dc_bias += D->m_cos_table[j];
+ }
+ for (j=0; j<D->ms_filter_size; j++) {
+ D->m_cos_table[j] -= dc_bias / D->ms_filter_size;
+ }
+
+
+ dc_bias = 0;
+ for (j=0; j<D->ms_filter_size; j++) {
+ dc_bias += D->s_sin_table[j];
+ }
+ for (j=0; j<D->ms_filter_size; j++) {
+ D->s_sin_table[j] -= dc_bias / D->ms_filter_size;
+ }
+
+ dc_bias = 0;
+ for (j=0; j<D->ms_filter_size; j++) {
+ dc_bias += D->s_cos_table[j];
+ }
+ for (j=0; j<D->ms_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<size; i+=SPARSE) {
+ dw_printf ("\t%c (%.6f * x[%d]) \\\n", (i==0 ? ' ' : '+'), coeff[i], i);
+ }
+ dw_printf ("\n");
+}
+
+int main ()
+{
+ //int n;
+ char fff_profile;
+
+ fff_profile = 'F';
+
+ memset (&modem, 0, sizeof(modem));
+ memset (&ds, 0, sizeof(ds));
+
+ modem.num_channels = 1;
+ modem.samples_per_sec = DEFAULT_SAMPLES_PER_SEC;
+ modem.mark_freq[0] = DEFAULT_MARK_FREQ;
+ modem.space_freq[0] = DEFAULT_SPACE_FREQ;
+ modem.baud[0] = DEFAULT_BAUD;
+ modem.num_subchan[0] = 1;
+
+
+ demod_afsk_init (modem.samples_per_sec, modem.baud[0],
+ modem.mark_freq[0], modem.space_freq[0], fff_profile, &ds);
+
+ printf ("/* This is an automatically generated file. Do not edit. */\n");
+ printf ("\n");
+ printf ("#define FFF_SAMPLES_PER_SEC %d\n", modem.samples_per_sec);
+ printf ("#define FFF_BAUD %d\n", modem.baud[0]);
+ printf ("#define FFF_MARK_FREQ %d\n", modem.mark_freq[0]);
+ printf ("#define FFF_SPACE_FREQ %d\n", modem.space_freq[0]);
+ printf ("#define FFF_PROFILE '%c'\n", fff_profile);
+ printf ("\n");
+
+ emit_macro ("CALC_M_SUM1", ds.ms_filter_size, ds.m_sin_table);
+ emit_macro ("CALC_M_SUM2", ds.ms_filter_size, ds.m_cos_table);
+ emit_macro ("CALC_S_SUM1", ds.ms_filter_size, ds.s_sin_table);
+ emit_macro ("CALC_S_SUM2", ds.ms_filter_size, ds.s_cos_table);
+
+ exit(0);
+}
+
+#endif
+
+
+
+#ifndef GEN_FFF
+
+/* Optimization for slow processors. */
+
+#include "fsk_fast_filter.h"
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name: demod_afsk_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_afsk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D)
+{
+ float fsam, abs_fsam;
+ float m_sum1, m_sum2, s_sum1, s_sum2;
+ float m_amp, s_amp;
+ float m_norm, s_norm;
+ float demod_out;
+#if DEBUG4
+ static FILE *demod_log_fp = NULL;
+ static int seq = 0; /* for log file name */
+#endif
+
+ int j;
+ int demod_data;
+
+ 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? 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 100755
index 0000000..0969467
--- /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 100755
index 0000000..dff36e9
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+//#include <ctype.h> /* for isdigit */
+#include "regex.h"
+#include <sys/unistd.h>
+
+#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<my_config.num_chans; to_chan++) {
+ if (my_config.enabled[from_chan][to_chan]) {
+ if (to_chan == from_chan) {
+ result = digipeat_match (pp, my_config.mycall[from_chan], my_config.mycall[to_chan],
+ &my_config.alias[from_chan][to_chan], &my_config.wide[from_chan][to_chan],
+ to_chan, my_config.preempt[from_chan][to_chan]);
+ if (result != NULL) {
+ dedupe_remember (pp, to_chan);
+ tq_append (to_chan, TQ_PRIO_0_HI, result);
+ }
+ }
+ }
+ }
+
+
+/*
+ * Second pass: Look at packets being digipeated to different channel.
+ *
+ * These are lower priority
+ */
+
+ for (to_chan=0; to_chan<my_config.num_chans; to_chan++) {
+ if (my_config.enabled[from_chan][to_chan]) {
+ if (to_chan != from_chan) {
+ result = digipeat_match (pp, my_config.mycall[from_chan], my_config.mycall[to_chan],
+ &my_config.alias[from_chan][to_chan], &my_config.wide[from_chan][to_chan],
+ to_chan, my_config.preempt[from_chan][to_chan]);
+ if (result != NULL) {
+ dedupe_remember (pp, to_chan);
+ tq_append (to_chan, TQ_PRIO_1_LO, result);
+ }
+ }
+ }
+ }
+
+} /* end digipeater */
+
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name: digipeat_match
+ *
+ * Purpose: A simple digipeater for APRS.
+ *
+ * Input: pp - Pointer to a packet object.
+ *
+ * mycall_rec - Call of my station, with optional SSID,
+ * associated with the radio channel where the
+ * packet was received.
+ *
+ * mycall_rec - Call of my station, with optional SSID,
+ * associated with the radio channel where the
+ * packet was received. Could be the same as
+ * mycall_rec or different.
+ *
+ * alias - Compiled pattern for my station aliases or
+ * "trapping" (repeating only once).
+ *
+ * wide - Compiled pattern for normal WIDEn-n digipeating.
+ *
+ * to_chan - Channel number that we are transmitting to.
+ * This is needed to maintain a history for
+ * removing duplicates during specified time period.
+ *
+ preempt - Option for "preemptive" digipeating.
+ *
+ * Returns: Packet object for transmission or NULL.
+ *
+ * Description: The packet will be digipeated if the next unused digipeater
+ * field matches one of the following:
+ *
+ * - mycall_rec
+ * - udigi list (only once)
+ * - wide list (usual wideN-N rules)
+ *
+ *------------------------------------------------------------------------------*/
+
+static char *dest_ssid_path[16] = {
+ "", /* Use VIA path */
+ "WIDE1-1",
+ "WIDE2-2",
+ "WIDE3-3",
+ "WIDE4-4",
+ "WIDE5-5",
+ "WIDE6-6",
+ "WIDE7-7",
+ "WIDE1-1", /* North */
+ "WIDE1-1", /* South */
+ "WIDE1-1", /* East */
+ "WIDE1-1", /* West */
+ "WIDE2-2", /* North */
+ "WIDE2-2", /* South */
+ "WIDE2-2", /* East */
+ "WIDE2-2" }; /* West */
+
+
+static packet_t digipeat_match (packet_t pp, char *mycall_rec, char *mycall_xmit,
+ regex_t *alias, regex_t *wide, int to_chan, enum preempt_e preempt)
+{
+ int ssid;
+ int r;
+ char repeater[AX25_MAX_ADDR_LEN];
+ packet_t result = NULL;
+ int err;
+ char err_msg[100];
+
+
+/*
+ * The spec says:
+ *
+ * The SSID in the Destination Address field of all packets is coded to specify
+ * the APRS digipeater path.
+ * If the Destination Address SSID is �0, the packet follows the standard AX.25
+ * digipeater (�VIA�) path contained in the Digipeater Addresses field of the
+ * AX.25 frame.
+ * If the Destination Address SSID is non-zero, the packet follows one of 15
+ * generic APRS digipeater paths.
+ *
+ *
+ * What if this is non-zero but there is also a digipeater path?
+ * I will ignore this if there is an explicit path.
+ *
+ * Note that this modifies the input. But only once!
+ * Otherwise we don't want to modify the input because this could be called multiple times.
+ */
+
+ if (ax25_get_num_repeaters(pp) == 0 && (ssid = ax25_get_ssid(pp, AX25_DESTINATION)) > 0) {
+ ax25_set_addr(pp, AX25_REPEATER_1, dest_ssid_path[ssid]);
+ ax25_set_ssid(pp, AX25_DESTINATION, 0);
+ /* Continue with general case, below. */
+ }
+
+/*
+ * 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.<platform> 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 100755
index 0000000..48f4e1c
--- /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 100755
index 0000000..2edf403
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdio.h>
+#include <math.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <assert.h>
+#include <string.h>
+#include <signal.h>
+
+#if __WIN32__
+#else
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/soundcard.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#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<argc-1; j++) {
+ if (strcmp(argv[j], "-t") == 0) {
+ t_opt = atoi (argv[j+1]);
+ //dw_printf ("DEBUG: text color option = %d.\n", t_opt);
+ }
+ }
+
+ text_color_init(t_opt);
+ text_color_set(DW_COLOR_INFO);
+ //dw_printf ("Dire Wolf version %d.%d (%s) Beta Test 2\n", MAJOR_VERSION, MINOR_VERSION, __DATE__);
+ //dw_printf ("Dire Wolf version %d.%d (%s) Development version\n", MAJOR_VERSION, MINOR_VERSION, __DATE__);
+
+ // Note "a" for fix with beacon sent to IGate Server.
+
+ dw_printf ("Dire Wolf version %d.%da\n", MAJOR_VERSION, MINOR_VERSION);
+
+
+#if __WIN32__
+ SetConsoleCtrlHandler (cleanup_win, TRUE);
+#else
+ setlinebuf (stdout);
+ signal (SIGINT, cleanup_linux);
+#endif
+
+
+/*
+ * Starting with version 0.9, the prebuilt Windows version
+ * requires a minimum of a Pentium 3 or equivalent so we can
+ * use the SSE instructions.
+ * Try to warn anyone using a CPU from the previous
+ * century rather than just dying for no apparent reason.
+ *
+ * Now, where can I find a Pentium 2 or earlier to test this?
+ */
+
+#if __SSE__
+ int cpuinfo[4];
+ __cpuid (cpuinfo, 0);
+ if (cpuinfo[0] >= 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<modem.num_channels; c++)
+ {
+ audio_sample = demod_get_sample ();
+
+ if (audio_sample >= 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 100755
index 0000000..87aa694
--- /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 100755
index 0000000..218874f
--- /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 100755
index 0000000..e78c3a5
--- /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 <windows.h>
+#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 <pthread.h>
+#endif
\ No newline at end of file
diff --git a/dsp.c b/dsp.c
new file mode 100755
index 0000000..9f58726
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: dsp.c
+ *
+ * Purpose: Generate the filters used by the demodulators.
+ *
+ *----------------------------------------------------------------*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#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<filter_size; j++) {
+ float center;
+ float sinc;
+ float shape;
+
+ center = 0.5 * (filter_size - 1);
+
+ if (j - center == 0) {
+ sinc = 2 * fc;
+ }
+ else {
+ sinc = sin(2 * M_PI * fc * (j-center)) / (M_PI*(j-center));
+ }
+
+ shape = window (wtype, filter_size, j);
+ lp_filter[j] = sinc * shape;
+
+#if DEBUG1
+ dw_printf ("%6d %6.2f %6.3f %6.3f\n", j, shape, sinc, lp_filter[j] ) ;
+#endif
+ }
+
+/*
+ * Normalize lowpass for unity gain.
+ */
+ lp_sum = 0;
+ for (j=0; j<filter_size; j++) {
+ lp_sum += lp_filter[j];
+ }
+ for (j=0; j<filter_size; j++) {
+ lp_filter[j] = lp_filter[j] / lp_sum;
+ }
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: gen_bandpass
+ *
+ * Purpose: Generate band pass filter kernel.
+ *
+ * Inputs: f1 - Lower cutoff frequency as fraction of sampling frequency.
+ * f2 - Upper cutoff frequency...
+ * filter_size - Number of filter taps.
+ * wtype - Window type, BP_WINDOW_HAMMING, etc.
+ *
+ * Outputs: bp_filter
+ *
+ * Reference: http://www.labbookpages.co.uk/audio/firWindowing.html
+ *
+ * Does it need to be an odd length?
+ *
+ *----------------------------------------------------------------*/
+
+
+void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_window_t wtype)
+{
+ int j;
+ float bp_sum;
+
+
+#if DEBUG1
+ text_color_set(DW_COLOR_DEBUG);
+
+ dw_printf ("Bandpass, size=%d\n", filter_size);
+ dw_printf (" j shape sinc final\n");
+#endif
+
+ assert (filter_size >= 3 && filter_size <= MAX_FILTER_SIZE);
+
+ for (j=0; j<filter_size; j++) {
+ float center;
+ float sinc;
+ float shape;
+
+ center = 0.5 * (filter_size - 1);
+
+ if (j - center == 0) {
+ sinc = 2 * (f2 - f1);
+ }
+ else {
+ sinc = sin(2 * M_PI * f2 * (j-center)) / (M_PI*(j-center))
+ - sin(2 * M_PI * f1 * (j-center)) / (M_PI*(j-center));
+ }
+
+ shape = window (wtype, filter_size, j);
+ bp_filter[j] = sinc * shape;
+
+#if DEBUG1
+ dw_printf ("%6d %6.2f %6.3f %6.3f\n", j, shape, sinc, bp_filter[j] ) ;
+#endif
+ }
+
+/*
+ * Normalize bandpass for unity gain.
+ */
+ bp_sum = 0;
+ for (j=0; j<filter_size; j++) {
+ bp_sum += bp_filter[j];
+ }
+ for (j=0; j<filter_size; j++) {
+ bp_filter[j] = bp_filter[j] / bp_sum;
+ }
+}
+
+/* end dsp.c */
\ No newline at end of file
diff --git a/dsp.h b/dsp.h
new file mode 100755
index 0000000..1150714
--- /dev/null
+++ b/dsp.h
@@ -0,0 +1,10 @@
+
+/* dsp.h */
+
+// TODO: put prefixes on these names.
+
+float window (bp_window_t type, int size, int j);
+
+void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype);
+
+void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_window_t wtype);
\ No newline at end of file
diff --git a/dtmf.c b/dtmf.c
new file mode 100755
index 0000000..70dc60b
--- /dev/null
+++ b/dtmf.c
@@ -0,0 +1,411 @@
+//
+// 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 <http://www.gnu.org/licenses/>.
+//
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <assert.h>
+
+#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<NUM_TONES; j++) {
+ float k;
+
+
+// Why do some insist on rounding k to the nearest integer?
+// That would move the filter center frequency away from ideal.
+// What is to be gained?
+// More consistent results for all the tones when k is not rounded off.
+
+ k = D.block_size * (float)(dtmf_tones[j]) / (float)(D.sample_rate);
+
+ D.coef[j] = 2 * cos(2 * M_PI * (float)k / (float)(D.block_size));
+
+ assert (D.coef[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<MAX_CHANS; c++) {
+ D.C[c].n = 0;
+ for (j=0; j<NUM_TONES; j++) {
+ D.C[c].Q1[j] = 0;
+ D.C[c].Q2[j] = 0;
+ }
+ D.C[c].prev_dec = ' ';
+ D.C[c].debounced = ' ';
+ D.C[c].prev_debounced = ' ';
+ D.C[c].timeout = 0;
+ }
+
+}
+
+/*------------------------------------------------------------------
+ *
+ * Name: dtmf_sample
+ *
+ * Purpose: Process one audio sample from the sound input source.
+ *
+ * Inputs: c - Audio channel number.
+ * This can process multiple channels in parallel.
+ * input - Audio sample.
+ *
+ * Returns: 0123456789ABCD*# for a button push.
+ * . for nothing happening during sample interval.
+ * $ after several seconds of inactivity.
+ * space between sample intervals.
+ *
+ *
+ *----------------------------------------------------------------*/
+
+__attribute__((hot))
+char dtmf_sample (int c, float input)
+{
+ int i;
+ float Q0;
+ float output[NUM_TONES];
+ char decoded;
+ char ret;
+ static const char rc2char[16] = { '1', '2', '3', 'A',
+ '4', '5', '6', 'B',
+ '7', '8', '9', 'C',
+ '*', '0', '#', 'D' };
+ for (i=0; i<NUM_TONES; i++) {
+ Q0 = input + D.C[c].Q1[i] * D.coef[i] - D.C[c].Q2[i];
+ D.C[c].Q2[i] = D.C[c].Q1[i];
+ D.C[c].Q1[i] = Q0;
+ }
+
+/*
+ * Is it time to process the block?
+ */
+ D.C[c].n++;
+ if (D.C[c].n == D.block_size) {
+ int row, col;
+
+ for (i=0; i<NUM_TONES; i++) {
+ output[i] = sqrt(D.C[c].Q1[i] * D.C[c].Q1[i] + D.C[c].Q2[i] * D.C[c].Q2[i] - D.C[c].Q1[i] * D.C[c].Q2[i] * D.coef[i]);
+ D.C[c].Q1[i] = 0;
+ D.C[c].Q2[i] = 0;
+ }
+ D.C[c].n = 0;
+
+
+/*
+ * The input signal can vary over a couple orders of
+ * magnitude so we can't set some absolute threshold.
+ *
+ * See if one tone is stronger than the sum of the
+ * others in the same group multiplied by some factor.
+ *
+ * For perfect synthetic signals this needs to be in
+ * the range of about 1.33 (very senstive) to 2.15 (very fussy).
+ *
+ * Too low will cause false triggers on random noise.
+ * Too high will won't decode less than perfect signals.
+ *
+ * Use the mid point 1.74 as our initial guess.
+ * It might need some fine tuning for imperfect real world signals.
+ */
+
+
+#define THRESHOLD 1.74
+
+ if (output[0] > THRESHOLD * ( output[1] + output[2] + output[3])) row = 0;
+ else if (output[1] > THRESHOLD * (output[0] + output[2] + output[3])) row = 1;
+ else if (output[2] > THRESHOLD * (output[0] + output[1] + output[3])) row = 2;
+ else if (output[3] > THRESHOLD * (output[0] + output[1] + output[2] )) row = 3;
+ else row = -1;
+
+ if (output[4] > THRESHOLD * ( output[5] + output[6] + output[7])) col = 0;
+ else if (output[5] > THRESHOLD * (output[4] + output[6] + output[7])) col = 1;
+ else if (output[6] > THRESHOLD * (output[4] + output[5] + output[7])) col = 2;
+ else if (output[7] > THRESHOLD * (output[4] + output[5] + output[6] )) col = 3;
+ else col = -1;
+
+ for (i=0; i<NUM_TONES; i++) {
+#if DEBUG
+ dw_printf ("%5.0f ", output[i]);
+#endif
+ }
+ if (row >= 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 100755
index 0000000..78eb28a
--- /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 100755
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 100755
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 100755
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 100755
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 100755
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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/time.h>
+
+#if __WIN32__
+#include <windows.h>
+#else
+#if ENABLE_GPS
+#include <gps.h>
+
+#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 <windows.h>
+#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 100755
index 0000000..ffb563d
--- /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 100755
index 0000000..638ac2d
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <time.h>
+#include <math.h>
+#include <assert.h>
+
+#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<Hn[& ") != 0) dw_printf ("ERROR!\n");
+
+
+/* with PHG. In this case it is converted to precomputed radio range. */
+
+ 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 TBD ???") != 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");
+
+
+
+/*********** Object. ***********/
+
+
+ encode_object ("WB1GOF-C", 0, 0, 42+34.61/60, -(71+26.47/60), 'D', '&',
+ 0, 0, 0, NULL, result);
+ dw_printf ("%s\n", result);
+ if (strcmp(result, ";WB1GOF-C *111111z4234.61ND07126.47W& TBD???") != 0) dw_printf ("ERROR!\n");
+
+
+ return(0);
+
+} /* end main */
+
+#endif /* unit test */
+
+
+/* end encode_aprs.c */
+
diff --git a/encode_aprs.h b/encode_aprs.h
new file mode 100755
index 0000000..1d3c828
--- /dev/null
+++ b/encode_aprs.h
@@ -0,0 +1,16 @@
+
+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 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);
+
diff --git a/fcs_calc.c b/fcs_calc.c
new file mode 100755
index 0000000..d2e9cfe
--- /dev/null
+++ b/fcs_calc.c
@@ -0,0 +1,108 @@
+//
+// 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 <http://www.gnu.org/licenses/>.
+//
+
+
+#include <stdio.h>
+
+/*
+ * 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<len; j++) {
+
+ crc = ((crc) >> 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<len; j++) {
+
+ crc = ((crc) >> 8) ^ ccitt_table[((crc) ^ data[j]) & 0xff];
+ }
+
+ return ( crc ^ 0xffff );
+}
+
diff --git a/fcs_calc.h b/fcs_calc.h
new file mode 100755
index 0000000..fa0c34b
--- /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 100755
index 0000000..c4353c9
--- /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 100755
index 0000000..9185361
--- /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 100755
index 0000000..0786462
--- /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 100755
index 0000000..608c69b
--- /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 100755
index 0000000..28014c4
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <string.h>
+#include <assert.h>
+
+#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; c<modem.num_channels; c++)
+ {
+ hdlc_send_flags (c, 8, 0);
+ hdlc_send_frame (c, fbuf, flen);
+ hdlc_send_flags (c, 2, 1);
+ }
+ ax25_delete (pp);
+ }
+ }
+ else {
+
+/*
+ * Builtin default 4 packets.
+ */
+ pp = ax25_from_text ("WB2OSZ-1>APRS,W1AB-9,W1ABC-10,WB1ABC-15:,Hello, world!", 1);
+ flen = ax25_pack (pp, fbuf);
+ for (c=0; c<modem.num_channels; c++)
+ {
+ hdlc_send_flags (c, 8, 0);
+ hdlc_send_frame (c, fbuf, flen);
+ hdlc_send_flags (c, 2, 1);
+ }
+ ax25_delete (pp);
+
+ hdlc_send_flags (c, 8, 0);
+
+ pp = ax25_from_text ("WB2OSZ-1>APRS,W1AB-9*,W1ABC-10,WB1ABC-15:,Hello, world!", 1);
+ flen = ax25_pack (pp, fbuf);
+ for (c=0; c<modem.num_channels; c++)
+ {
+ hdlc_send_frame (c, fbuf, flen);
+ }
+ ax25_delete (pp);
+
+ pp = ax25_from_text ("WB2OSZ-1>APRS,W1AB-9,W1ABC-10*,WB1ABC-15:,Hello, world!", 1);
+ flen = ax25_pack (pp, fbuf);
+ for (c=0; c<modem.num_channels; c++)
+ {
+ hdlc_send_frame (c, fbuf, flen);
+ }
+ ax25_delete (pp);
+
+ pp = ax25_from_text ("WB2OSZ-1>APRS,W1AB-9,W1ABC-10,WB1ABC-15*:,Hello, world!", 1);
+ flen = ax25_pack (pp, fbuf);
+ for (c=0; c<modem.num_channels; c++)
+ {
+ hdlc_send_frame (c, fbuf, flen);
+ }
+ ax25_delete (pp);
+
+ hdlc_send_flags (c, 2, 1);
+
+ }
+
+ audio_file_close();
+
+ return EXIT_SUCCESS;
+}
+
+
+static void usage (char **argv)
+{
+
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("\n");
+ dw_printf ("Usage: xxx [options] [file]\n");
+ dw_printf ("Options:\n");
+ dw_printf (" -a <number> Signal amplitude in range of 0 - 100%%. Default 50.\n");
+ dw_printf (" -b <number> Bits / second for data. Default is %d.\n", DEFAULT_BAUD);
+ dw_printf (" -B <number> Bits / second for data. Proper modem selected for 300, 1200, 9600.\n");
+ dw_printf (" -g Scrambled baseband rather than AFSK.\n");
+ dw_printf (" -m <number> Mark frequency. Default is %d.\n", DEFAULT_MARK_FREQ);
+ dw_printf (" -s <number> Space frequency. Default is %d.\n", DEFAULT_SPACE_FREQ);
+ dw_printf (" -r <number> Audio sample Rate. Default is %d.\n", DEFAULT_SAMPLES_PER_SEC);
+ dw_printf (" -n <number> Generate specified number of frames with increasing noise.\n");
+ dw_printf (" -o <file> 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> 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 100755
index 0000000..39b3bf0
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module: gen_tone.c
+ *
+ * Purpose: Convert bits to AFSK for writing to .WAV sound file
+ * or a sound device.
+ *
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <math.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#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<audio_param.baud[0] * 2 ; n++) {
+ tone_gen_put_bit ( chan1, 1 );
+ }
+
+ for (n=0; n<audio_param.baud[0] * 2 ; n++) {
+ tone_gen_put_bit ( chan1, 0 );
+ }
+ }
+
+ audio_close();
+
+/* Now try stereo. */
+
+ memset (&audio_param, 0, sizeof(audio_param));
+ strcpy (audio_param.adevice_in, DEFAULT_ADEVICE);
+ strcpy (audio_param.adevice_out, DEFAULT_ADEVICE);
+ audio_param.num_channels = 2;
+
+ audio_open (&audio_param);
+ gen_tone_init (&audio_param, 100);
+
+ for (r=0; r<4; r++) {
+
+ for (n=0; n<audio_param.baud[0] * 2 ; n++) {
+ tone_gen_put_bit ( chan1, 1 );
+ }
+
+ for (n=0; n<audio_param.baud[0] * 2 ; n++) {
+ tone_gen_put_bit ( chan1, 0 );
+ }
+
+ for (n=0; n<audio_param.baud[0] * 2 ; n++) {
+ tone_gen_put_bit ( chan2, 1 );
+ }
+
+ for (n=0; n<audio_param.baud[0] * 2 ; n++) {
+ tone_gen_put_bit ( chan2, 0 );
+ }
+ }
+
+ audio_close();
+
+ return(0);
+}
+
+#endif
+
+
+/* end gen_tone.c */
diff --git a/gen_tone.h b/gen_tone.h
new file mode 100755
index 0000000..b4b3f30
--- /dev/null
+++ b/gen_tone.h
@@ -0,0 +1,16 @@
+/*
+ * gen_tone.h
+ */
+
+
+int gen_tone_init (struct audio_s *pp, int amp);
+
+
+//int gen_tone_open (int nchan, int sample_rate, int bit_rate, int f1, int f2, int amp, char *fname);
+
+//int gen_tone_open_fd (int nchan, int sample_rate, int bit_rate, int f1, int f2, int amp, int fd) ;
+
+//int gen_tone_close (void);
+
+void tone_gen_put_bit (int chan, int dat);
+
diff --git a/hdlc_rec.c b/hdlc_rec.c
new file mode 100755
index 0000000..0a5c7f3
--- /dev/null
+++ b/hdlc_rec.c
@@ -0,0 +1,513 @@
+//
+// 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 <http://www.gnu.org/licenses/>.
+//
+
+
+/********************************************************************************
+ *
+ * File: hdlc_rec.c
+ *
+ * Purpose: Extract HDLC frames from a stream of bits.
+ *
+ *******************************************************************************/
+
+#include <stdio.h>
+#include <assert.h>
+
+#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; j<pa->num_channels; j++)
+ {
+ num_subchan[j] = pa->num_subchan[j];
+
+ assert (num_subchan[j] >= 1 && num_subchan[j] < MAX_SUBCHANS);
+
+ for (k=0; k<MAX_SUBCHANS; k++)
+ {
+ H = &hdlc_state[j][k];
+
+ H->prev_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; j<H->frame_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 100755
index 0000000..8ef86f0
--- /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 100755
index 0000000..629fa31
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/********************************************************************************
+ *
+ * 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 <stdio.h>
+#include <assert.h>
+#include <ctype.h>
+
+#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<len; i++) {
+ ok = try_decode (block, chan, subchan, alevel, RETRY_SINGLE, i, -1, -1);
+ if (ok) {
+#if DEBUG
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("*** Success by flipping SINGLE bit %d of %d ***\n", i, len);
+#endif
+ return 1;
+ }
+ }
+
+/*
+ * Try fixing two adjacent bits.
+ */
+ if (fix_bits < RETRY_DOUBLE) {
+ return 0;
+ }
+
+ for (i=0; i<len-1; i++) {
+ ok = try_decode (block, chan, subchan, alevel, RETRY_DOUBLE, i, i+1, -1);
+ if (ok) {
+#if DEBUG
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("*** Success by flipping DOUBLE bit %d of %d ***\n", i, len);
+#endif
+ return 1;
+ }
+ }
+
+/*
+ * Try fixing adjacent three bits.
+ */
+ if (fix_bits < RETRY_TRIPLE) {
+ return 0;
+ }
+
+ len = rrbb_get_len(block);
+ for (i=0; i<len-2; i++) {
+ ok = try_decode (block, chan, subchan, alevel, RETRY_TRIPLE, i, i+1, i+2);
+ if (ok) {
+#if DEBUG
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("*** Success by flipping TRIPLE bit %d of %d ***\n", i, len);
+#endif
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, int alevel)
+{
+ int ok;
+ int len, i;
+#if DEBUG
+ double tstart, tend;
+#endif
+
+ len = rrbb_get_len(block);
+
+/*
+ * Two non-adjacent ("separated") single bits.
+ * It chews up a lot of CPU time. Test takes 4 times longer to run.
+ *
+ * Ran up to 4.82 seconds for 1040 bits before giving up.
+ * Processing time is order N squared so time goes up rapidly with larger frames.
+ */
+
+#if DEBUG
+ tstart = dtime_now();
+#endif
+ len = rrbb_get_len(block);
+ for (i=0; i<len-2; i++) {
+ int j;
+
+ ok = 0;
+ for (j=i+2; j<len; j++) {
+ ok = try_decode (block, chan, subchan, alevel, RETRY_TWO_SEP, i, j, -1);
+ if (ok)
+ break;
+ }
+ if (ok) {
+#if DEBUG
+ tend = dtime_now();
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("*** Success by flipping TWO SEPARATED bits %d and %d of %d *** %.3f sec.\n", i, j, len, tend-tstart);
+#endif
+ return;
+ }
+ }
+#if DEBUGx
+ tend = dtime_now();
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("*** No luck flipping TWO SEPARATED bits of %d *** %.3f sec.\n", len, tend-tstart);
+#endif
+
+ return;
+}
+
+
+
+
+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)
+{
+ struct hdlc_state_s H;
+ int blen; /* Block length in bits. */
+ int i;
+ int raw; /* From demodulator. */
+ int dbit; /* Data bit after undoing NRZI. */
+
+
+ H.prev_raw = rrbb_get_bit (block, 0); /* Actually last bit of the */
+ /* opening flag so we can derive the */
+ /* first data bit. */
+
+ /* Does this make sense? */
+ /* This is the last bit of the "flag" pattern. */
+ /* If it was corrupted we wouldn't have detected */
+ /* the start of frame. */
+
+ if (0 == flip_a || 0 == flip_b || 0 == flip_c){
+ H.prev_raw = ! H.prev_raw;
+ }
+
+ H.pat_det = 0;
+ H.oacc = 0;
+ H.olen = 0;
+ H.frame_len = 0;
+
+ blen = rrbb_get_len (block);
+
+#if DEBUGx
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("try_decode: blen=%d\n", blen);
+#endif
+
+ for (i=1; i<blen; i++) {
+
+ raw = rrbb_get_bit (block, i);
+
+ if (i == flip_a || i == flip_b || i == flip_c){
+ raw = ! raw;
+ }
+
+/*
+ * 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;
+ }
+
+ 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<H.frame_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 && sanity_check (H.frame_buf, H.frame_len - 2, bits_flipped)) {
+
+
+ // TODO: Shouldn't be necessary to pass chan, subchan, alevel into
+ // try_decode because we can obtain them from block.
+ // Let's make sure that assumption is good...
+
+ assert (rrbb_get_chan(block) == chan);
+ assert (rrbb_get_subchan(block) == subchan);
+ assert (rrbb_get_audio_level(block) == alevel);
+
+ multi_modem_process_rec_frame (chan, subchan, H.frame_buf, H.frame_len - 2, alevel, bits_flipped); /* len-2 to remove FCS. */
+ return 1; /* success */
+ }
+ }
+ return 0; /* failure. */
+
+} /* end try_decode */
+
+
+/*
+ * Try to weed out bogus packets from initially failed FCS matches.
+ */
+
+static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped)
+{
+ int alen; /* Length of address part. */
+ int j;
+
+/*
+ * No sanity check if we didn't try fixing the data.
+ * Should we have different levels of checking depending on
+ * how much we try changing the raw data?
+ */
+ if (bits_flipped == RETRY_NONE) {
+ return 1;
+ }
+
+#if DEBUGx
+ text_color_set(DW_COLOR_XMIT);
+ dw_printf ("sanity_check: address part length = %d\n", alen);
+#endif
+
+/*
+ * Address part must be a multiple of 7.
+ */
+
+ alen = 0;
+ for (j=0; j<blen && alen==0; j++) {
+ if (buf[j] & 0x01) {
+ alen = j + 1;
+ }
+ }
+
+ if (alen % 7 != 0) {
+#if DEBUGx
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("sanity_check: FAILED. Address part not multiple of 7.\n");
+#endif
+ return 0;
+ }
+
+/*
+ * Need at least 2 addresses and maximum of 8 digipeaters.
+ */
+
+ if (alen/7 < 2 || alen/7 > 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<alen; j+=7) {
+
+ char addr[7];
+
+ addr[0] = buf[j+0] >> 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<blen; j++) {
+ int ch = buf[j];
+
+ if ( ! (( ch >= 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 100755
index 0000000..9cef6ca
--- /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 100755
index 0000000..1c4ffe8
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+#include <stdio.h>
+
+#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<flen; j++) {
+ send_data (chan, fbuf[j]);
+ }
+
+ fcs = fcs_calc (fbuf, flen);
+
+ send_data (chan, fcs & 0xff);
+ send_data (chan, (fcs >> 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<nflags; j++) {
+ send_control (chan, 0x7e);
+ }
+
+/* Push out the final partial buffer! */
+
+ if (finish) {
+ audio_flush();
+ }
+
+ return (number_of_bits_sent);
+}
+
+
+
+static int stuff = 0;
+
+static void send_control (int chan, int x)
+{
+ int i;
+
+ for (i=0; i<8; i++) {
+ send_bit (chan, x & 1);
+ x >>= 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 100755
index 0000000..dc6c304
--- /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 100755
index 0000000..5f4601a
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <winsock2.h>
+// 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 <ws2tcpip.h>
+#else
+#include <stdlib.h>
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#endif
+
+#include <unistd.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include <string.h>
+
+
+#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; j<g_num_chans; j++) {
+ strcpy (g_mycall[j], p_digi_config->mycall[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<nhosts; j++) {
+ k = rand() % nhosts;
+ assert (k >=0 && k<nhosts);
+ if (j != k) {
+ struct addrinfo *temp;
+ temp = host[j]; host[j] = host[k]; host[k] = temp;
+ }
+ }
+}
+
+#define MAX_HOSTS 50
+
+
+
+
+#if __WIN32__
+static unsigned __stdcall connnect_thread (void *arg)
+#else
+static void * connnect_thread (void *arg)
+#endif
+{
+ struct addrinfo hints;
+ struct addrinfo *ai_head = NULL;
+ struct addrinfo *ai;
+ struct addrinfo *hosts[MAX_HOSTS];
+ int num_hosts, n;
+ int err;
+ char server_port_str[12]; /* text form of port number */
+ char ipaddr_str[46]; /* text form of IP address */
+#if __WIN32__
+ WSADATA wsadata;
+#endif
+
+ sprintf (server_port_str, "%d", g_config.t2_server_port);
+#if DEBUGx
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("DEBUG: igate connect_thread start, port = %d = '%s'\n", g_config.t2_server_port, server_port_str);
+#endif
+
+#if __WIN32__
+ err = WSAStartup (MAKEWORD(2,2), &wsadata);
+ if (err != 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("WSAStartup failed: %d\n", err);
+ return (0);
+ }
+
+ if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("Could not find a usable version of Winsock.dll\n");
+ WSACleanup();
+ //sleep (1);
+ return (0);
+ }
+#endif
+
+ memset (&hints, 0, sizeof(hints));
+
+ hints.ai_family = AF_UNSPEC; /* Allow either IPv4 or IPv6. */
+
+ // IPv6 is half baked on Windows XP.
+ // We might need to leave out IPv6 support for Windows version.
+ // hints.ai_family = AF_INET; /* IPv4 only. */
+
+#if IPV6_ONLY
+ /* IPv6 addresses always show up at end of list. */
+ /* Force use of them for testing. */
+ hints.ai_family = AF_INET6; /* IPv6 only */
+#endif
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+
+/*
+ * Repeat forever.
+ */
+
+ while (1) {
+
+/*
+ * Connect to IGate server if not currently connected.
+ */
+
+ if (igate_sock == -1) {
+
+ SLEEP_SEC (5);
+
+ ai_head = NULL;
+ err = getaddrinfo(g_config.t2_server_name, server_port_str, &hints, &ai_head);
+ if (err != 0) {
+ text_color_set(DW_COLOR_ERROR);
+#if __WIN32__
+ dw_printf ("Can't get address for IGate server %s, err=%d\n",
+ g_config.t2_server_name, WSAGetLastError());
+#else
+ dw_printf ("Can't get address for IGate server %s, %s\n",
+ g_config.t2_server_name, gai_strerror(err));
+#endif
+ freeaddrinfo(ai_head);
+
+ continue;
+ }
+
+#if DEBUG_DNS
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("getaddrinfo returns:\n");
+#endif
+ num_hosts = 0;
+ for (ai = ai_head; ai != NULL; ai = ai->ai_next) {
+#if DEBUG_DNS
+ text_color_set(DW_COLOR_DEBUG);
+ ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
+ dw_printf (" %s\n", ipaddr_str);
+#endif
+ hosts[num_hosts] = ai;
+ if (num_hosts < MAX_HOSTS) num_hosts++;
+ }
+
+ // We can get multiple addresses back for the host name.
+ // These should be somewhat randomized for load balancing.
+ // It turns out the IPv6 addresses are always at the
+ // end for both Windows and Linux. We do our own shuffling
+ // to mix them up better and give IPv6 a chance.
+
+ shuffle (hosts, num_hosts);
+
+#if DEBUG_DNS
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("after shuffling:\n");
+ for (n=0; n<num_hosts; n++) {
+ ia_to_text (hosts[n]->ai_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; n<num_hosts; n++) {
+ int is;
+
+ ai = hosts[n];
+
+ ia_to_text (ai->ai_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; n++) {
+ rx2ig_time_stamp[n] = 0;
+ rx2ig_checksum[n] = 0;
+ }
+ rx2ig_insert_next = 0;
+}
+
+
+static void rx_to_ig_remember (packet_t pp)
+{
+ rx2ig_time_stamp[rx2ig_insert_next] = time(NULL);
+ rx2ig_checksum[rx2ig_insert_next] = ax25_dedupe_crc(pp);
+
+ rx2ig_insert_next++;
+ if (rx2ig_insert_next >= RX2IG_HISTORY_MAX) {
+ rx2ig_insert_next = 0;
+ }
+}
+
+static int rx_to_ig_allow (packet_t pp)
+{
+ unsigned short crc = ax25_dedupe_crc(pp);
+ time_t now = time(NULL);
+ int j;
+
+ for (j=0; j<RX2IG_HISTORY_MAX; j++) {
+ if (rx2ig_time_stamp[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; n++) {
+ ig2tx_time_stamp[n] = 0;
+ ig2tx_checksum[n] = 0;
+ }
+ ig2tx_insert_next = 0;
+}
+
+
+static void ig_to_tx_remember (packet_t pp)
+{
+ ig2tx_time_stamp[ig2tx_insert_next] = time(NULL);
+ ig2tx_checksum[ig2tx_insert_next] = ax25_dedupe_crc(pp);
+
+ ig2tx_insert_next++;
+ if (ig2tx_insert_next >= 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<IG2TX_HISTORY_MAX; j++) {
+ if (ig2tx_time_stamp[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<IG2TX_HISTORY_MAX; j++) {
+ if (ig2tx_time_stamp[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 100755
index 0000000..aa47507
--- /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 100755
index 0000000..b3a10b4
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdio.h>
+#include <unistd.h>
+
+#if __WIN32__
+#include <stdlib.h>
+#include <windows.h>
+#else
+#define __USE_XOPEN2KXSI 1
+#define __USE_XOPEN 1
+//#define __USE_POSIX 1
+#include <stdlib.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/errno.h>
+#endif
+
+#include <assert.h>
+#include <string.h>
+
+#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<flen; j++) {
+
+ if (fbuf[j] == FEND) {
+ kiss_buff[kiss_len++] = FESC;
+ kiss_buff[kiss_len++] = TFEND;
+ }
+ else if (fbuf[j] == FESC) {
+ kiss_buff[kiss_len++] = FESC;
+ kiss_buff[kiss_len++] = TFESC;
+ }
+ else {
+ kiss_buff[kiss_len++] = fbuf[j];
+ }
+ assert (kiss_len < sizeof (kiss_buff));
+ }
+ kiss_buff[kiss_len++] = FEND;
+
+ /* This has the escapes but not the surrounding FENDs. */
+
+ if (kiss_debug) {
+ kiss_debug_print (TO_CLIENT, NULL, kiss_buff+1, kiss_len-2);
+ }
+
+ }
+
+#if ! __WIN32__
+
+/* Pseudo terminal for Cygwin and Linux. */
+
+
+ err = write (pt_master_fd, kiss_buff, (size_t)kiss_len);
+
+ if (err == -1 && errno == EWOULDBLOCK) {
+#if DEBUG
+ text_color_set (DW_COLOR_INFO);
+ dw_printf ("KISS SEND - discarding message because write would block.\n");
+#endif
+ }
+ else if (err != kiss_len)
+ {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("\nError sending KISS message to client application on pseudo terminal. fd=%d, len=%d, write returned %d, errno = %d\n\n",
+ pt_master_fd, kiss_len, err, errno);
+ perror ("pt write");
+ }
+
+#endif
+
+#if __CYGWIN__ || __WIN32__
+
+
+/*
+ * This write can block if nothing is connected to the other end.
+ * The solution is found in the com0com ReadMe file:
+ *
+ * Q. My application hangs during its startup when it sends anything to one paired
+ * COM port. The only way to unhang it is to start HyperTerminal, which is connected
+ * to the other paired COM port. I didn't have this problem with physical serial
+ * ports.
+ * A. Your application can hang because receive buffer overrun is disabled by
+ * default. You can fix the problem by enabling receive buffer overrun for the
+ * receiving port. Also, to prevent some flow control issues you need to enable
+ * baud rate emulation for the sending port. So, if your application use port CNCA0
+ * and other paired port is CNCB0, then:
+ *
+ * 1. Launch the Setup Command Prompt shortcut.
+ * 2. Enter the change commands, for example:
+ *
+ * command> change CNCB0 EmuOverrun=yes
+ * command> change CNCA0 EmuBR=yes
+ */
+
+#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 100755
index 0000000..83563c4
--- /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 100755
index 0000000..5d3d066
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdio.h>
+#include <unistd.h>
+
+#include <stdlib.h>
+#include <ctype.h>
+
+#include <assert.h>
+#include <string.h>
+
+#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 100755
index 0000000..4ecd0a4
--- /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 100755
index 0000000..91712ee
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <winsock2.h>
+#define _WIN32_WINNT 0x0501
+#include <ws2tcpip.h>
+#else
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif
+
+#include <unistd.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include <string.h>
+
+
+#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<flen; j++) {
+
+ if (fbuf[j] == FEND) {
+ kiss_buff[kiss_len++] = FESC;
+ kiss_buff[kiss_len++] = TFEND;
+ }
+ else if (fbuf[j] == FESC) {
+ kiss_buff[kiss_len++] = FESC;
+ kiss_buff[kiss_len++] = TFESC;
+ }
+ else {
+ kiss_buff[kiss_len++] = fbuf[j];
+ }
+ assert (kiss_len < sizeof (kiss_buff));
+ }
+ kiss_buff[kiss_len++] = FEND;
+
+ /* Bug: This has the escapes but not the surrounding FENDs. */
+
+ if (kiss_debug) {
+ kiss_debug_print (TO_CLIENT, NULL, kiss_buff+1, kiss_len-2);
+ }
+ }
+
+#if __WIN32__
+ err = send (client_sock, (char*)kiss_buff, kiss_len, 0);
+ if (err == SOCKET_ERROR)
+ {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("\nError %d sending message to KISS client application. Closing connection.\n\n", WSAGetLastError());
+ closesocket (client_sock);
+ client_sock = -1;
+ WSACleanup();
+ }
+#else
+ err = write (client_sock, kiss_buff, kiss_len);
+ if (err <= 0)
+ {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("\nError sending message to KISS client application. Closing connection.\n\n");
+ close (client_sock);
+ client_sock = -1;
+ }
+#endif
+
+} /* end kissnet_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.
+ *
+ * Not really needed for KISS because we are dealing with
+ * a stream of bytes rather than message blocks.
+ *
+ *--------------------------------------------------------------------*/
+
+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: 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 100755
index 0000000..2b4f9b9
--- /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 100755
index 0000000..a31adb6
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <time.h>
+#include <math.h>
+#include <assert.h>
+
+#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 100755
index 0000000..c55b590
--- /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 100755
index 0000000..a1858c9
--- /dev/null
+++ b/ll2utm.c
@@ -0,0 +1,55 @@
+/* Latitude / Longitude to UTM conversion */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#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 100755
index 0000000..a6373bc
--- /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 100755
index 0000000..6cadc2a
--- /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 <sys/cdefs.h>
+
+#include <ctype.h>
+#include <string.h>
+
+/*
+ * 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 100755
index 0000000..37181b0
--- /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 <string.h>
+//#include <_ansi.h>
+//#include <reent.h>
+
+#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 100755
index 0000000..d56a4e4
--- /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 <string.h>
+
+#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 100755
index 0000000..df67e3d
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module: morse.c
+ *
+ * Purpose: Generate audio for morse code.
+ *
+ * Description:
+ *
+ * Reference:
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <sys/time.h>
+
+#if __WIN32__
+#include <windows.h>
+#else
+#include <sys/termios.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#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<tu; n++) {
+ dw_printf ("#");
+ }
+
+
+
+} /* end morse_tone */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name: morse_quiet
+ *
+ * Purpose: Generate silence for specified number of time units.
+ *
+ * Inputs: tu - Number of time units.
+ *
+ *--------------------------------------------------------------------*/
+
+static void morse_quiet (int tu) {
+ int n;
+
+ for (n=0; n<tu; n++) {
+ dw_printf (".");
+ }
+
+} /* end morse_quiet */
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name: morse_lookup
+ *
+ * Purpose: Given a character, find index in table above.
+ *
+ * Inputs: ch
+ *
+ * Returns: Index into table above or -1 if not found.
+ * Notice that space is not in the table.
+ * Any unusual character, that is not in the table,
+ * ends up being treated like space.
+ *
+ *--------------------------------------------------------------------*/
+
+static int morse_lookup (int ch)
+{
+ int i;
+
+ if (islower(ch)) {
+ ch = toupper(ch);
+ }
+
+ for (i=0; i<NUM_MORSE; i++) {
+ if (ch == morse[i].ch) {
+ return (i);
+ }
+ }
+ return (-1);
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name: morse_units_ch
+ *
+ * Purpose: Find number of time units for a character.
+ *
+ * Inputs: ch
+ *
+ * Returns: 1 for E (.)
+ * 3 for T (-)
+ * 3 for I.= (..)
+ * etc.
+ *
+ * The one unexpected result is 1 for space. Why not 7?
+ * When a space appears between two other characters,
+ * we already have 3 before and after so only 1 more is needed.
+ *
+ *--------------------------------------------------------------------*/
+
+static int morse_units_ch (int ch)
+{
+ int i;
+ int len;
+ int k;
+ int units;
+
+ i = morse_lookup (ch);
+ if (i < 0) {
+ return (1); /* space or any invalid character */
+ }
+
+
+ len = strlen(morse[i].enc);
+ units = len - 1;
+
+ for (k = 0; k < len; k++) {
+ switch (morse[i].enc[k]) {
+ case '.': units++; break;
+ case '-': units += 3; break;
+ default: dw_printf ("ERROR: morse_units_ch: should not be here.\n"); break;
+ }
+ }
+
+ return (units);
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name: morse_units_str
+ *
+ * Purpose: Find number of time units for a string of characters.
+ *
+ * Inputs: str
+ *
+ * Returns: 1 for E
+ * 5 for EE (1 + 3 + 1)
+ * 9 for E E (1 + 7 + 1)
+ * etc.
+ *
+ *--------------------------------------------------------------------*/
+
+static int morse_units_str (char *str)
+{
+ int i;
+ int len;
+ int k;
+ int units;
+
+ len = strlen(str);
+ units = (len - 1) * 3;
+
+ for (k = 0; k < len; k++) {
+ units += morse_units_ch(str[k]);
+ }
+
+ return (units);
+}
+
+
+int main (int argc, char *argv[]) {
+
+ dw_printf ("CQ DX\n");
+ morse_send (0, "CQ DX", 10, 10, 10);
+ dw_printf ("\n\n");
+
+ dw_printf ("wb2osz/9\n");
+ morse_send (0, "wb2osz/9", 10, 10, 10);
+ dw_printf ("\n\n");
+
+}
+
+
+/* end morse.c */
+
+
+
diff --git a/multi_modem.c b/multi_modem.c
new file mode 100755
index 0000000..aae42f0
--- /dev/null
+++ b/multi_modem.c
@@ -0,0 +1,484 @@
+//
+// 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 <http://www.gnu.org/licenses/>.
+//
+
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+#include <sys/unistd.h>
+
+#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<modem.num_channels; chan++) {
+ process_age[chan] = PROCESS_AFTER_BITS * modem.samples_per_sec / modem.baud[chan];
+ crc_of_last_to_app[chan] = 0x12345678;
+ }
+
+}
+
+
+
+/*------------------------------------------------------------------------------
+ *
+ * Name: multi_modem_process_sample
+ *
+ * Purpose: Feed the sample into the proper modem(s) for the channel.
+ *
+ * Inputs: chan - Radio channel number
+ *
+ * audio_sample
+ *
+ *------------------------------------------------------------------------------*/
+
+
+__attribute__((hot))
+void multi_modem_process_sample (int chan, int audio_sample)
+{
+ int subchan;
+
+ for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) {
+ demod_process_sample(chan, subchan, audio_sample);
+
+ if (candidate[chan][subchan].packet_p != NULL) {
+ candidate[chan][subchan].age++;
+ if (candidate[chan][subchan].age > process_age[chan]) {
+ pick_best_candidate (chan);
+ }
+ }
+ }
+}
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name: multi_modem_process_rec_frame
+ *
+ * Purpose: This is called when we receive a frame with a valid
+ * FCS and acceptable size.
+ *
+ * Inputs: chan - Audio channel number, 0 or 1.
+ * subchan - Which modem/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 100755
index 0000000..fb5ea05
--- /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 100755
index 0000000..01c77f3
--- /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 <http://www.gnu.org/licenses/>.
+
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <sys/time.h>
+
+#if __WIN32__
+#include <windows.h>
+#else
+#include <sys/termios.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* 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; ch<ptt_num_channels; ch++) {
+
+ ptt_method[ch] = p_modem->ptt_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<ptt_num_channels; ch++) {
+
+ if (ptt_method[ch] == PTT_METHOD_SERIAL) {
+
+#if __WIN32__
+#else
+ /* Translate Windows device name into Linux name. */
+ /* COM1 -> /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<ptt_num_channels; ch++) {
+ if (ptt_method[ch] == PTT_METHOD_GPIO) {
+ using_gpio = 1;
+ }
+ }
+
+ if (using_gpio) {
+
+ struct stat finfo;
+/*
+ * Normally the device nodes are set up for access
+ * only by root. Try to change it here so we don't
+ * burden user with another configuration step.
+ *
+ * Does /sys/class/gpio/export even exist?
+ */
+
+ if (stat("/sys/class/gpio/export", &finfo) < 0) {
+ 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);
+ }
+
+/*
+ * Do we have permission to access it?
+ *
+ * pi at raspberrypi /sys/class/gpio $ ls -l
+ * total 0
+ * --w------- 1 root root 4096 Aug 20 07:59 export
+ * lrwxrwxrwx 1 root root 0 Aug 20 07:59 gpiochip0 -> ../../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<ptt_num_channels; ch++) {
+ if (ptt_method[ch] == PTT_METHOD_GPIO) {
+ char stemp[80];
+ struct stat finfo;
+
+ fd = open("/sys/class/gpio/export", O_WRONLY);
+ if (fd < 0) {
+ 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);
+ }
+ sprintf (stemp, "%d", ptt_gpio[ch]);
+ if (write (fd, stemp, strlen(stemp)) != strlen(stemp)) {
+ int e = errno;
+ /* Ignore EBUSY error which seems to mean */
+ /* the device node already exists. */
+ if (e != EBUSY) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Error writing \"%s\" to /sys/class/gpio/export, errno=%d\n", stemp, e);
+ dw_printf ("%s\n", strerror(e));
+ exit (1);
+ }
+ }
+ close (fd);
+
+/*
+ * We will have the same permission problem if not root.
+ * We only care about "direction" and "value".
+ */
+ sprintf (stemp, "sudo chmod go+rw /sys/class/gpio/gpio%d/direction", ptt_gpio[ch]);
+ system (stemp);
+ sprintf (stemp, "sudo chmod go+rw /sys/class/gpio/gpio%d/value", ptt_gpio[ch]);
+ system (stemp);
+
+ sprintf (stemp, "/sys/class/gpio/gpio%d/value", ptt_gpio[ch]);
+
+ if (stat(stemp, &finfo) < 0) {
+ int e = errno;
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Failed to get status for %s \n", stemp);
+ dw_printf ("%s\n", strerror(e));
+ exit (1);
+ }
+
+ if (geteuid() != 0) {
+ 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 these commands:\n");
+ dw_printf (" chmod go+rw /sys/class/gpio/gpio%d/direction", ptt_gpio[ch]);
+ dw_printf (" chmod go+rw /sys/class/gpio/gpio%d/value", ptt_gpio[ch]);
+ exit (1);
+ }
+ }
+
+/*
+ * Set output direction with initial state off.
+ */
+
+ sprintf (stemp, "/sys/class/gpio/gpio%d/direction", ptt_gpio[ch]);
+ fd = open(stemp, O_WRONLY);
+ if (fd < 0) {
+ int e = errno;
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Error opening %s\n", stemp);
+ dw_printf ("%s\n", strerror(e));
+ exit (1);
+ }
+
+ char hilo[8];
+ if (ptt_invert[ch]) {
+ strcpy (hilo, "high");
+ }
+ else {
+ strcpy (hilo, "low");
+ }
+ if (write (fd, hilo, strlen(hilo)) != strlen(hilo)) {
+ int e = errno;
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Error writing initial state to %s\n", stemp);
+ dw_printf ("%s\n", strerror(e));
+ exit (1);
+ }
+ close (fd);
+ }
+ }
+#endif
+
+
+/* Why doesn't it transmit? Probably forgot to specify PTT option. */
+
+ for (ch=0; ch<ptt_num_channels; ch++) {
+ if(ptt_method[ch] == PTT_METHOD_NONE) {
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("Note: PTT not configured for channel %d.\n", ch);
+ }
+ }
+} /* end ptt_init */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name: ptt_set
+ *
+ * Purpose: Turn transmitter on or off.
+ *
+ * Inputs: chan channel, 0 .. (number of channels)-1
+ *
+ * ptt 1 for transmit, 0 for receive.
+ *
+ *
+ * Assumption: ptt_init was called first.
+ *
+ * Description: Set the RTS or DTR line or GPIO pin.
+ * More positive output means transmit unless invert is set.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void ptt_set (int chan, int ptt)
+{
+
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("ptt_set ( %d, %d )\n", chan, ptt);
+#endif
+
+/*
+ * Inverted output?
+ */
+
+ if (ptt_invert[chan]) {
+ ptt = ! ptt;
+ }
+
+ assert (chan >= 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 100755
index 0000000..7c27a20
--- /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 100755
index 0000000..e894561
--- /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 <stdio.h>
+#include <unistd.h>
+
+
+#define __USE_XOPEN2KXSI 1
+#define __USE_XOPEN 1
+//#define __USE_POSIX 1
+#include <stdlib.h>
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/errno.h>
+
+#include <assert.h>
+#include <string.h>
+
+//#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 100755
index 0000000..57f18f8
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Module: rdq.c
+ *
+ * Purpose: Retry later decode queue for frames with bad FCS.
+ *
+ * Description:
+ *
+ *---------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#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 100755
index 0000000..dcb2896
--- /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 100755
index 0000000..0a90702
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+//#include <sys/time.h>
+#include <time.h>
+
+#if __WIN32__
+#include <windows.h>
+#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 100755
index 0000000..87995b7
--- /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 100755
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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 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.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/regex/COPYING.LIB b/regex/COPYING.LIB
new file mode 100755
index 0000000..cf9b6b9
--- /dev/null
+++ b/regex/COPYING.LIB
@@ -0,0 +1,510 @@
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations
+below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+^L
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it
+becomes a de-facto standard. To achieve this, non-free programs must
+be allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+^L
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control
+compilation and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+^L
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+^L
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at least
+ three years, to give the same user the materials specified in
+ Subsection 6a, above, for a charge no more than the cost of
+ performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+^L
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+^L
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply, and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License
+may add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+^L
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+^L
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms
+of the ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library.
+It is safest to attach them to the start of each source file to most
+effectively convey the exclusion of warranty; and each file should
+have at least the "copyright" line and a pointer to where the full
+notice is found.
+
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or
+your school, if any, to sign a "copyright disclaimer" for the library,
+if necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James
+ Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/regex/INSTALL b/regex/INSTALL
new file mode 100755
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 100755
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 at 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 100755
index 0000000..e69de29
diff --git a/regex/README b/regex/README
new file mode 100755
index 0000000..e69de29
diff --git a/regex/README-dire-wolf.txt b/regex/README-dire-wolf.txt
new file mode 100755
index 0000000..58ca0b9
--- /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 100755
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 <regex.h> file. XPG4.2 mentions
+ this name. */
+#include <regex.h>
+
+#endif /* re_comp.h */
diff --git a/regex/regcomp.c b/regex/regcomp.c
new file mode 100755
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 <isamu at yamato.ibm.com>.
+
+ 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 <reg_exp>:
+ CAT
+ / \
+ / \
+ <reg_exp> 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
+ <branch1>|<branch2>:
+ ALT
+ / \
+ / \
+ <branch1> <branch2>
+
+ 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
+ <exp1><exp2>:
+ CAT
+ / \
+ / \
+ <exp1> <exp2>
+
+ 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 "<ANCHOR(^)><CHAR(*)>",
+ it must not be "<ANCHOR(^)><REPEAT(*)>". */
+ 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
+ (<reg_exp>):
+ SUBEXP
+ |
+ <reg_exp>
+*/
+
+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; /* <re>{} 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 "<re>{n,m}" to "<re><re>...<re><re>{0,<m-n>}". */
+ 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 <re>{0,n} as (<re>(<re>...<re>?)?)?... 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 [:<character_class>:], [.<collating_element>.], and
+ [=<equivalent_class>=]. */
+
+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 <locale/weight.h>
+ /* 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 100755
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 <isamu at yamato.ibm.com>.
+
+ 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 <regex.h>, which correctly
+ #undefs RE_DUP_MAX and sets it to the right value. */
+#include <limits.h>
+
+#include <regex.h>
+#include "regex_internal.h"
+
+#include "regex_internal.c"
+#include "regcomp.c"
+#include "regexec.c"
+
+/* Binary backward compatibility. */
+#if _LIBC
+# include <shlib-compat.h>
+# 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 100755
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 <sys/types.h>
+
+#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 \<digit> matches <digit>.
+ If not set, then \<digit> 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 100755
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 <isamu at yamato.ibm.com>.
+
+ 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:
+ <mb1>(0), <mb1>(1), <mb2>(0), <mb2>(1), <sb3>
+ Then wide character buffer will be:
+ <wc1> , WEOF , <wc2> , WEOF , <wc3>
+ 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 100755
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 <isamu at yamato.ibm.com>.
+
+ 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 <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined HAVE_LANGINFO_H || defined HAVE_LANGINFO_CODESET || defined _LIBC
+# include <langinfo.h>
+#endif
+#if defined HAVE_LOCALE_H || defined _LIBC
+# include <locale.h>
+#endif
+#if defined HAVE_WCHAR_H || defined _LIBC
+# include <wchar.h>
+#endif /* HAVE_WCHAR_H || _LIBC */
+#if defined HAVE_WCTYPE_H || defined _LIBC
+# include <wctype.h>
+#endif /* HAVE_WCTYPE_H || _LIBC */
+#if defined HAVE_STDBOOL_H || defined _LIBC
+# include <stdbool.h>
+#endif /* HAVE_STDBOOL_H || _LIBC */
+#if defined HAVE_STDINT_H || defined _LIBC
+# include <stdint.h>
+#endif /* HAVE_STDINT_H || _LIBC */
+#if defined _LIBC
+# include <bits/libc-lock.h>
+#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 <locale/localeinfo.h>
+# include <locale/elem-hash.h>
+# include <locale/coll-lookup.h>
+# endif
+#endif
+
+/* This is for other GNU distributions with internationalized messages. */
+#if (HAVE_LIBINTL_H && ENABLE_NLS) || defined _LIBC
+# include <libintl.h>
+# 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 <alloca.h>
+#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 <locale/weight.h>
+ 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 100755
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 <isamu at yamato.ibm.com>.
+
+ 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 <shlib-compat.h>
+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:
+ <src> <dst> ( <subexp> )
+ ( <subexp> ) <src> <dst>
+ ( <subexp1> <src> <subexp2> <dst> <subexp3> ) */
+ 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 <locale/weight.h>
+
+ /* 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 100755
index 0000000..36a780a
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/********************************************************************************
+ *
+ * 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 <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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; i<b->len; 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; i<b->len; 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 100755
index 0000000..ae78bb1
--- /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 100755
index 0000000..5462197
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <winsock2.h>
+#define _WIN32_WINNT 0x0501
+#include <ws2tcpip.h>
+#else
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif
+
+#include <unistd.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <time.h>
+#include <ctype.h>
+
+#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<n; i++) {
+ dw_printf (" %02x", p[i]);
+ }
+ for (i=n; i<16; i++) {
+ dw_printf (" ");
+ }
+ dw_printf (" ");
+ for (i=0; i<n; i++) {
+ dw_printf ("%c", isprint(p[i]) ? p[i] : '.');
+ }
+ dw_printf ("\n");
+ p += 16;
+ offset += 16;
+ len -= 16;
+ }
+}
+
+typedef enum fromto_e { FROM_CLIENT=0, TO_CLIENT=1 } fromto_t;
+
+static void debug_print (fromto_t fromto, struct agwpe_s *pmsg, int msg_len)
+{
+ char direction [10];
+ char datakind[80];
+ const char *prefix [2] = { "<<<", ">>>" };
+
+ 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 <UI pid=%02X Len=%d >[%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<ndigi; k++) {
+ strcat (stemp, ",");
+ strcat (stemp, p);
+ p += 10;
+ }
+ strcat (stemp, ":");
+ strcat (stemp, p);
+
+ //text_color_set(DW_COLOR_DEBUG);
+ //dw_printf ("Transmit '%s'\n", stemp);
+
+ pp = ax25_from_text (stemp, 1);
+
+
+ if (pp == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Failed to create frame from AGW 'V' message.\n");
+ }
+ else {
+
+ /* This goes into the low priority queue because it is an original. */
+
+ /* Note that the protocol has no way to set the "has been used" */
+ /* bits in the digipeater fields. */
+
+ /* This explains why the digipeating option is grayed out in */
+ /* xastir when using the AGW interface. */
+ /* The current version uses only the 'V' message, not 'K' for transmitting. */
+
+ tq_append (cmd.hdr.portx, TQ_PRIO_1_LO, pp);
+
+ }
+ }
+
+ break;
+
+ case 'K': /* Transmit raw AX.25 frame */
+ {
+ // Message contains:
+ // port number for transmission.
+ // data length
+ // data which is raw ax.25 frame.
+ //
+
+
+ packet_t pp;
+
+ pp = ax25_from_frame ((unsigned char *)cmd.data+1, cmd.hdr.data_len, -1);
+
+ if (pp == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Failed to create frame from AGW 'K' message.\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 (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 100755
index 0000000..2a75b18
--- /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 100755
index 0000000..6b81aee
--- /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 100755
index 0000000..1ba1773
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * File: symbols.c
+ *
+ * Purpose: Functions related to the APRS symbols
+ *
+ *------------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#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<new_sym_len; j++) {
+ dw_printf ("%02d: %c %c '%s'\n", j, new_sym_ptr[j].overlay,
+ new_sym_ptr[j].symbol, new_sym_ptr[j].description);
+ }
+#endif
+
+} /* end symbols_init */
+
+
+/*------------------------------------------------------------------
+ *
+ * Function: symbols_from_dest_or_src
+ *
+ * Purpose: Extract symbol from destination or source.
+ *
+ * Inputs: dti - Data type indicator.
+ *
+ * src - Source address with SSID.
+ *
+ * dest - Destination address.
+ * Don't care if SSID is present or not.
+ *
+ * Outputs: *symtab
+ * *symbol
+ *
+ * Description: There are 3 different ways to specify the symbol,
+ * in this order of precedence:
+ *
+ * - Information field. This was done already in the
+ * separate decoders for different message types.
+ *
+ * If not set already,
+ *
+ * - The destination address if it has certain formats
+ * starting with GPS, SPC, or SYM which are equivalent
+ * for our purposes.
+ * (Not for MIC-E where destination has a special use.)
+ *
+ * If all else fails,
+ *
+ * - SSID of the source address.
+ *
+ *
+ *------------------------------------------------------------------*/
+
+const static char ssid_to_sym[16] = {
+ ' ', /* 0 - No icon. */
+ 'a', /* 1 - Ambulance */
+ 'U', /* 2 - Bus */
+ 'f', /* 3 - Fire Truck */
+ 'b', /* 4 - Bicycle */
+ 'Y', /* 5 - Yacht */
+ 'X', /* 6 - Helicopter */
+ '\'', /* 7 - Small Aircraft */
+ 's', /* 8 - Ship */
+ '>', /* 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; j<new_sym_len; j++) {
+ if (symtab == new_sym_ptr[j].overlay && symbol == new_sym_ptr[j].symbol) {
+ strcpy (description, new_sym_ptr[j].description);
+ return;
+ }
+ }
+
+// Otherwise use the original symbol tables.
+
+ if (symtab == '/') {
+ strcpy (description, primary_symtab[symbol-' '].description);
+ }
+ else {
+ strcpy (description, alternate_symtab[symbol-' '].description);
+ if (symtab != '\\') {
+ strcat (description, " w/overlay ");
+ tmp2[0] = symtab;
+ tmp2[1] = '\0';
+ strcat (description, tmp2);
+ }
+ }
+
+} /* end symbols_get_description */
+
+
+/*------------------------------------------------------------------
+ *
+ * Function: symbols_code_from_description
+ *
+ * Purpose: Find a suitable table/symbol based on given description.
+ *
+ * Inputs: overlay Explicit overlay or space.
+ * description Substring of one of the descriptions.
+ * Example: dog
+ *
+ * Outputs: symtab /, \, 0-9, A-Z
+ * symbol any printable character ! to ~
+ *
+ * Returns: 1 for success, 0 for failure.
+ *
+ *------------------------------------------------------------------*/
+
+int symbols_code_from_description (char overlay, char *description, char *symtab, char *symbol)
+{
+ int j;
+
+ symbols_init();
+
+/*
+ * If user specified a particular overlay (i.e. for config file BEACON),
+ * first try the alternate symbol table.
+ * If that fails should we give up or ignore the overlay and keep trying?
+ */
+
+ if (isupper(overlay) || isdigit(overlay)) {
+
+ for (j=0; j<SYMTAB_SIZE; j++) {
+ if (strcasestr(alternate_symtab[j].description, description) != NULL) {
+ *symtab = overlay;
+ *symbol = j + ' ';
+ return (1);
+ }
+ }
+ /* If that fails should we give up or ignore the overlay and keep trying? */
+ }
+
+/*
+ * Search primary table.
+ */
+ for (j=0; j<SYMTAB_SIZE; j++) {
+ if (strcasestr(primary_symtab[j].description, description) != NULL) {
+ *symtab = '/';
+ *symbol = j + ' ';
+ return (1);
+ }
+ }
+
+/*
+ * Search alternate table.
+ */
+ for (j=0; j<SYMTAB_SIZE; j++) {
+ if (strcasestr(alternate_symtab[j].description, description) != NULL) {
+ *symtab = '\\';
+ *symbol = j + ' ';
+ return (1);
+ }
+ }
+
+/*
+ * Finally, the "new" symbols.
+ * Probably want this last so get get the most standard and
+ * generic for queries such as "house" or ...
+ */
+ for (j=0; j<new_sym_len; j++) {
+ if (strcasestr(new_sym_ptr[j].description, description) != NULL) {
+ *symtab = new_sym_ptr[j].overlay;
+ *symbol = new_sym_ptr[j].symbol;
+ return (1);
+ }
+ }
+
+/*
+ * Default to something generic like house.
+ * Caller is responsible for issuing error message.
+ */
+ *symtab = '/';
+ *symbol = '-';
+ return (0);
+
+} /* end symbols_code_from_description */
+
+
+#if 0
+
+/* Quick, incomplete, unit test. */
+/* gcc -g symbols.c textcolor.c misc.a */
+
+int main (int argc, char *argv[])
+{
+ char symtab;
+ char symbol;
+ char dest[8];
+ char description[50];
+
+ symbols_init ();
+
+
+
+ symbols_from_dest_or_src ('T', "W1ABC", "GPSC43", &symtab, &symbol);
+ if (symtab != '/' || symbol != 'K') dw_printf ("ERROR 1-1\n");
+
+ symbols_from_dest_or_src ('T', "W1ABC", "GPSE87", &symtab, &symbol);
+ if (symtab != '\\' || symbol != 'w') dw_printf ("ERROR 1-2\n");
+
+ symbols_from_dest_or_src ('T', "W1ABC", "SPCBL", &symtab, &symbol);
+ if (symtab != '/' || symbol != '+') dw_printf ("ERROR 1-3\n");
+
+ symbols_from_dest_or_src ('T', "W1ABC", "SYMST", &symtab, &symbol);
+ if (symtab != '\\' || symbol != 't') dw_printf ("ERROR 1-4\n");
+
+ symbols_from_dest_or_src ('T', "W1ABC", "GPSOD9", &symtab, &symbol);
+ if (symtab != '9' || symbol != '#') dw_printf ("ERROR 1-5\n");
+
+ symbols_from_dest_or_src ('T', "W1ABC-14", "XXXXXX", &symtab, &symbol);
+ if (symtab != '/' || symbol != 'k') dw_printf ("ERROR 1-6\n");
+
+ symbols_from_dest_or_src ('T', "W1ABC", "GPS???", &symtab, &symbol);
+ /* Outputs are left alone if symbol can't be determined. */
+ if (symtab != '/' || symbol != 'k') dw_printf ("ERROR 1-7\n");
+
+
+ symbols_into_dest ('/', 'K', dest);
+ if (strcmp(dest, "GPSC43") != 0) dw_printf ("ERROR 2-1\n");
+
+ symbols_into_dest ('\\', 'w', dest);
+ if (strcmp(dest, "GPSE87") != 0) dw_printf ("ERROR 2-2\n");
+
+ symbols_into_dest ('3', 'A', dest);
+ if (strcmp(dest, "GPSAA3") != 0) dw_printf ("ERROR 2-3\n");
+
+// Expect to see this:
+// Could not convert symbol " A" to GPSxyz destination format.
+// Could not convert symbol "/ " to GPSxyz destination format.
+
+ symbols_into_dest (' ', 'A', dest);
+ if (strcmp(dest, "GPS???") != 0) dw_printf ("ERROR 2-4\n");
+
+ symbols_into_dest ('/', ' ', dest);
+ if (strcmp(dest, "GPS???") != 0) dw_printf ("ERROR 2-5\n");
+
+
+
+ symbols_get_description ('J', 's', description);
+ if (strcmp(description, "Jet Ski") != 0) dw_printf ("ERROR 3-1\n");
+
+ symbols_get_description ('/', 'O', description);
+ if (strcmp(description, "BALLOON") != 0) dw_printf ("ERROR 3-2\n");
+
+ symbols_get_description ('\\', 'T', description);
+ if (strcmp(description, "Thunderstorm") != 0) dw_printf ("ERROR 3-3\n");
+
+ symbols_get_description ('5', 'T', description);
+ if (strcmp(description, "Thunderstorm w/overlay 5") != 0) dw_printf ("ERROR 3-4\n");
+
+// Expect to see this:
+// Symbol table identifier is not '/' (primary), '\' (alternate), or valid overlay character.
+
+ symbols_get_description (' ', 'T', description);
+ if (strcmp(description, "--no-symbol--") != 0) dw_printf ("ERROR 3-5\n");
+
+ symbols_get_description ('/', ' ', description);
+ if (strcmp(description, "--no-symbol--") != 0) dw_printf ("ERROR 3-6\n");
+
+
+
+ symbols_code_from_description ('5', "girl scouts", &symtab, &symbol);
+ if (symtab != '5' || symbol != ',') dw_printf ("ERROR 4-1\n");
+
+ symbols_code_from_description (' ', "scouts", &symtab, &symbol);
+ if (symtab != '/' || symbol != ',') dw_printf ("ERROR 4-2\n");
+
+ symbols_code_from_description (' ', "girl scouts", &symtab, &symbol);
+ if (symtab != '\\' || symbol != ',') dw_printf ("ERROR 4-3\n");
+
+ symbols_code_from_description (' ', "jet ski", &symtab, &symbol);
+ if (symtab != 'J' || symbol != 's') dw_printf ("ERROR 4-4\n");
+
+ symbols_code_from_description (' ', "girl scouts", &symtab, &symbol);
+ if (symtab != '\\' || symbol != ',') dw_printf ("ERROR 4-5\n");
+
+ symbols_code_from_description (' ', "yen", &symtab, &symbol);
+ if (symtab != 'Y' || symbol != '$') dw_printf ("ERROR 4-6\n");
+
+ symbols_code_from_description (' ', "taco bell", &symtab, &symbol);
+ if (symtab != 'T' || symbol != 'R') dw_printf ("ERROR 4-7\n");
+
+
+} /* end main */
+
+#endif
+
+/* end symbols.c */
\ No newline at end of file
diff --git a/symbols.h b/symbols.h
new file mode 100755
index 0000000..cf2eca3
--- /dev/null
+++ b/symbols.h
@@ -0,0 +1,14 @@
+
+/* symbols.h */
+
+void symbols_init (void);
+
+void symbols_from_dest_or_src (char dti, char *src, char *dest, char *symtab, char *symbol);
+
+int symbols_into_dest (char symtab, char symbol, char *dest);
+
+void symbols_get_description (char symtab, char symbol, char *description);
+
+int symbols_code_from_description (char overlay, char *description, char *symtab, char *symbol);
+
+/* end symbols.h */
diff --git a/symbolsX.txt b/symbolsX.txt
new file mode 100755
index 0000000..7e91724
--- /dev/null
+++ b/symbolsX.txt
@@ -0,0 +1,364 @@
+APRS SYMBOLS (Icons) 07 Oct 2013
+-----------------------------------------------------------------------
+ WB4APR
+
+This original APRS symbol specification is updated periodically with
+new symbols as they are defined. This is THE master list for APRS. But
+almost all new symbols will be formed as Overlays to the basic set. So
+be sure to check the symbols-new.txt file noted below!
+
+NEW OVERLAYS ON ALL ALTERNATE SYMBOLS: As of 1 October 2007, the use of
+overlay characters on all alternate symbols was allowed as needed in
+order to further expand the APRS symbol set. These overlay expansions
+of up to 36 different usages for each of the 94 alternate symbols adds
+hundreds of potential new symbols. Since this overlay info will no
+longer fit in this original document it is found in a new document:
+
+http://aprs.org/symbols/symbols-new.txt
+
+If an alternate symbol from the table below, contains significant
+definitions of its overlay characters, then the entry will have an "O"
+added to indicate that additonal definitions exist in the above
+document. The # symbol indicates the original Overlay subset.
+
+For details on Upgrading your symbol set, please see the background
+information on Symbols prepared by Stephen Smith, WA8LMF:
+http://www.ew.usna.edu/~bruninga/aprs/symbols-background.txt
+
+UPDATE CHRONOLOGY:
+
+07 Oct 13: Added JetSki [Js] & Ham Club [C-] ovrlys to symbols-new.txt
+19 Sep 11: Added T & 2 overlay for 1 & 2 hop Message TX Igates
+ Updated overlay "portable" (;) overlays for events
+20 Oct 09: Added WIRES to overlayed circles
+05 Dec 08: Clarified symbols classified as MOBILE.
+ Added Primary: 123456789\ and ALT: Y[\
+04 Nov 08: Added notes to match file with New Overlays noted above
+ Added list of "just-mobile-symbols"
+25 Mar 08: Added \% Power plants with Overlays. Changed
+ \i from "BOXn digi" to generic "BOX with overlay".
+ An indoor BOXn digi can use an overlay on existing digi.
+13 Feb 08: Added overlay "S" on \a and \c for SATERN
+10 Jan 08: Added Overlay "X" on BOX for the XO OLPC laptop
+01 Oct 07: Added the above paragraphs about Overlay byte extensions
+ of the APRS symbol set and defined two new symbols.
+ Added "S[" Skier and "O-" for House with Operator Present.
+03 Jul 07: Began defining some overlay common usages.
+09 Apr 07: First proposed expanding the alternate symbols overlays.
+ And added to the APRS1.2 addendum:
+ http://www.ew.usna.edu/~bruninga/aprs/symbols-overlays.txt
+02 Feb 07: Cleaned up some names for consistency with Icon tables
+18 Oct 06: Suggest \x for car wreck or road obstruction. Looks
+ like a big X like this: ->X<- 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 <bruninga at usna.edu>
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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 100755
index 0000000..8d3cf52
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*-------------------------------------------------------------------
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+
+#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 <windows.h>
+
+#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 100755
index 0000000..3d07e0b
--- /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 100755
index 0000000..a44bf2b
--- /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 100755
index 0000000..5b4c1d9
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#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<MAX_CHANS; c++) {
+ for (p=0; p<TQ_NUM_PRIO; p++) {
+ queue_head[c][p] = NULL;
+ }
+ }
+
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("tq_init: pthread_mutex_init...\n");
+#endif
+
+#if __WIN32__
+ InitializeCriticalSection (&tq_cs);
+#else
+ err = pthread_mutex_init (&wake_up_mutex, NULL);
+ err = pthread_mutex_init (&tq_mutex, NULL);
+ if (err != 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("tq_init: pthread_mutex_init err=%d", err);
+ perror ("");
+ exit (1);
+ }
+#endif
+
+
+
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("tq_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 ("tq_init: pthread_cond_init: can't create transmit wake up event");
+ exit (1);
+ }
+
+#else
+ err = pthread_cond_init (&wake_up_cond, NULL);
+
+
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("tq_init: pthread_cond_init returns %d\n", err);
+#endif
+
+
+ if (err != 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("tq_init: pthread_cond_init err=%d", err);
+ perror ("");
+ exit (1);
+ }
+
+ xmit_thread_is_waiting = 0;
+#endif
+
+
+} /* end tq_init */
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name: tq_append
+ *
+ * Purpose: Add a packet to the end of the specified transmit queue.
+ *
+ * Inputs: chan - Channel, 0 is first.
+ *
+ * prio - Priority, use TQ_PRIO_0_HI or TQ_PRIO_1_LO.
+ *
+ * pp - Address of packet object.
+ * Caller should NOT make any references to
+ * it after this point because it could
+ * be deleted at any time.
+ *
+ * Outputs:
+ *
+ * Description: Add packet to end of linked list.
+ * Signal the transmit thread if the queue was formerly empty.
+ *
+ * IMPORTANT! Don't make an further references to the packet object after
+ * giving it to tq_append.
+ *
+ *--------------------------------------------------------------------*/
+
+void tq_append (int chan, int prio, packet_t pp)
+{
+// int was_empty;
+// int c, p;
+ packet_t plast;
+ packet_t pnext;
+ int err;
+
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("tq_append (chan=%d, prio=%d, pp=%p)\n", chan, prio, pp);
+#endif
+ assert (tq_num_channels >= 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<tq_num_channels; c++) {
+// for (p=0; p<TQ_NUM_PRIO; p++) {
+// if (queue_head[c][p] != NULL)
+// was_empty = 0;
+// }
+// }
+
+ if (queue_head[chan][prio] == NULL) {
+ queue_head[chan][prio] = pp;
+ }
+ else {
+ plast = queue_head[chan][prio];
+ while ((pnext = ax25_get_nextp(plast)) != NULL) {
+ plast = pnext;
+ }
+ ax25_set_nextp (plast, pp);
+ }
+
+
+#if __WIN32__
+ LeaveCriticalSection (&tq_cs);
+#else
+ err = pthread_mutex_unlock (&tq_mutex);
+ if (err != 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("tq_append: pthread_mutex_unlock err=%d", err);
+ perror ("");
+ exit (1);
+ }
+#endif
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("tq_append: left critical section\n");
+ dw_printf ("tq_append (): about to wake up xmit thread.\n");
+#endif
+
+#if __WIN32__
+ SetEvent (wake_up_event);
+#else
+ if (xmit_thread_is_waiting) {
+
+ err = pthread_mutex_lock (&wake_up_mutex);
+ if (err != 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("tq_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 ("tq_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 ("tq_append: pthread_mutex_unlock wu err=%d", err);
+ perror ("");
+ exit (1);
+ }
+ }
+#endif
+
+}
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name: tq_wait_while_empty
+ *
+ * Purpose: Sleep while the transmit queue is empty rather than
+ * polling periodically.
+ *
+ * Inputs: None.
+ *
+ *--------------------------------------------------------------------*/
+
+
+void tq_wait_while_empty (void)
+{
+ int is_empty;
+ //int c, p;
+ int err;
+
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("tq_wait_while_empty () : enter critical section\n");
+#endif
+ assert (tq_num_channels >= 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<tq_num_channels; c++) {
+ for (p=0; p<TQ_NUM_PRIO; p++) {
+
+ assert (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 100755
index 0000000..c23173e
--- /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 100755
index 0000000..a7be809
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <stdarg.h>
+
+#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 100755
index 0000000..d2265d8
--- /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 100755
index 0000000..5a3439a
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <time.h>
+#include <assert.h>
+
+#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<MAX_TT_USERS; i++) {
+ clear_user (i);
+ }
+}
+
+/*------------------------------------------------------------------
+ *
+ * Name: tt_user_search
+ *
+ * Purpose: Search for user in recent history.
+ *
+ * Inputs: callsign - full or a suffix abbreviation
+ * overlay
+ *
+ * Returns: Handle for refering to table position or -1 if not found.
+ * This happens to be an index into an array but
+ * the implementation could change so the caller should
+ * not make any assumptions.
+ *
+ *----------------------------------------------------------------*/
+
+int tt_user_search (char *callsign, char overlay)
+{
+ int i;
+/*
+ * First, look for exact match to full call and overlay.
+ */
+ for (i=0; i<MAX_TT_USERS; i++) {
+ if (strcmp(callsign, tt_user[i].callsign) == 0 &&
+ overlay == tt_user[i].overlay) {
+ return (i);
+ }
+ }
+
+/*
+ * Look for digits only suffix plus overlay.
+ */
+ for (i=0; i<MAX_TT_USERS; i++) {
+ if (strcmp(callsign, tt_user[i].digit_suffix) == 0 &&
+ overlay != ' ' &&
+ overlay == tt_user[i].overlay) {
+ return (i);
+ }
+ }
+
+/*
+ * Look for digits only suffix if no overlay was specified.
+ */
+ for (i=0; i<MAX_TT_USERS; i++) {
+ if (strcmp(callsign, tt_user[i].digit_suffix) == 0 &&
+ overlay == ' ') {
+ return (i);
+ }
+ }
+
+/*
+ * Not sure about the new spelled suffix yet...
+ */
+ return (-1);
+
+} /* end tt_user_search */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: clear_user
+ *
+ * Purpose: Clear specified user table entry.
+ *
+ * Inputs: handle for user table entry.
+ *
+ *----------------------------------------------------------------*/
+
+static void clear_user(int i)
+{
+ assert (i >= 0 && i < MAX_TT_USERS);
+
+ memset (&tt_user[i], 0, sizeof (struct tt_user_s));
+
+} /* end clear_user */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: find_avail
+ *
+ * Purpose: Find an available user table location.
+ *
+ * Inputs: none
+ *
+ * Returns: Handle for refering to table position.
+ *
+ * Description: If table is already full, this should delete the
+ * least recently heard user to make room.
+ *
+ *----------------------------------------------------------------*/
+
+static int find_avail (void)
+{
+ int i;
+ int i_oldest;
+
+ for (i=0; i<MAX_TT_USERS; i++) {
+ if (tt_user[i].callsign[0] == '\0') {
+ clear_user (i);
+ return (i);
+ }
+ }
+
+/* Remove least recently heard. */
+
+ i_oldest = 0;
+
+ for (i=1; i<MAX_TT_USERS; i++) {
+ if (tt_user[i].last_heard < tt_user[i_oldest].last_heard) {
+ i_oldest = i;
+ }
+ }
+
+ clear_user (i_oldest);
+ return (i_oldest);
+
+} /* end find_avail */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: corral_slot
+ *
+ * Purpose: Find an available position in the corral.
+ *
+ * Inputs: none
+ *
+ * Returns: Small integer >= 1 not already in use.
+ *
+ *----------------------------------------------------------------*/
+
+static int corral_slot (void)
+{
+ int slot, i, used;
+
+ for (slot=1; ; slot++) {
+ used = 0;;
+ for (i=0; i<MAX_TT_USERS && ! used; i++) {
+ if (tt_user[i].callsign[0] != '\0' && tt_user[i].corral_slot == slot) {
+ used = 1;
+ }
+ }
+ if (!used) {
+ return (slot);
+ }
+ }
+
+} /* end corral_slot */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: digit_suffix
+ *
+ * Purpose: Find 3 digit only suffix code for given call.
+ *
+ * Inputs: callsign
+ *
+ * Outputs: 3 digit suffix
+ *
+ *----------------------------------------------------------------*/
+
+static void digit_suffix (char *callsign, char *suffix)
+{
+ char two_key[50];
+ char *t;
+
+
+ strcpy (suffix, "000");
+ tt_text_to_two_key (callsign, 0, two_key);
+ for (t = two_key; *t != '\0'; t++) {
+ if (isdigit(*t)) {
+ suffix[0] = suffix[1];
+ suffix[1] = suffix[2];
+ suffix[2] = *t;
+ }
+ }
+
+
+} /* end digit_suffix */
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: tt_user_heard
+ *
+ * Purpose: Record information from an APRStt trasmission.
+ *
+ * Inputs: callsign - full or an abbreviation
+ * ssid
+ * overlay - or symbol table identifier
+ * symbol
+ * latitude
+ * longitude
+ * freq
+ * comment
+ * mic_e
+ * dao
+ *
+ * Outputs: Information is stored in table above.
+ * Last heard time is updated.
+ * Object Report transmission is scheduled.
+ *
+ * Returns: 0 for success or one of the TT_ERROR_... codes.
+ *
+ *----------------------------------------------------------------*/
+
+int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, double latitude,
+ double longitude, char *freq, char *comment, char mic_e, char *dao)
+{
+ int i;
+
+/*
+ * At this time all messages are expected to contain a callsign.
+ * Other types of messages, not related to a particular person/object
+ * are a future possibility.
+ */
+ if (callsign[0] == '\0') {
+ text_color_set(DW_COLOR_ERROR);
+ printf ("APRStt message did not include callsign.\n");
+ return (TT_ERROR_NO_CALL);
+ }
+
+/*
+ * Is it someone new or a returning user?
+ */
+ i = tt_user_search (callsign, overlay);
+ if (i == -1) {
+
+/*
+ * New person. Create new table entry with all available information.
+ */
+ i = find_avail ();
+
+ assert (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<MAX_TT_USERS; i++) {
+ if (tt_user[i].callsign[0] != '\0') {
+ if (tt_user[i].xmits < tt_config.num_xmits && tt_user[i].next_xmit <= now) {
+
+ xmit_object_report (i, tt_config.corral_lat, tt_config.corral_lon,
+ tt_config.corral_ambiguity, tt_config.corral_offset);
+
+ /* Increase count of number times this one was sent. */
+ tt_user[i].xmits++;
+ if (tt_user[i].xmits < tt_config.num_xmits) {
+ /* Schedule next one. */
+ tt_user[i].next_xmit += tt_config.xmit_delay[tt_user[i].xmits];
+ }
+ }
+ }
+ }
+
+/*
+ * Purge if too old.
+ */
+ for (i=0; i<MAX_TT_USERS; i++) {
+ if (tt_user[i].callsign[0] != '\0') {
+ if (tt_user[i].last_heard + tt_config.retain_time < now) {
+
+//TODO: remove printf ("debug: purging expired user %d\n", i);
+
+ clear_user (i);
+ }
+ }
+ }
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: xmit_object_report
+ *
+ * Purpose: Create object report packet and put into transmit queue.
+ *
+ * Inputs: i - Index into user table.
+ * c_lat - Corral latitude.
+ * c_long - Corral longitude.
+ * ambiguity - Number of amibiguity digits: 0, 1, 2, or 3.
+ * c_offs - Corral (latitude) offset.
+ *
+ * Outputs: Append to transmit queue.
+ *
+ * Returns: None
+ *
+ * Description: Details for specified user are converted to
+ * "Object Report Format" and added to the transmit queue.
+ *
+ * If the user did not report a position, we have to make
+ * up something so the corresponding object will show up on
+ * the map or other list of nearby stations.
+ *
+ * The traditional approach is to put them in different
+ * positions in the "corral" by applying increments of an
+ * offset from the starting position. This has two
+ * unfortunate properties. It gives the illusion we know
+ * where the person is located. Being in the ,,,
+ *
+ *----------------------------------------------------------------*/
+
+static const char *mic_e_position_comment[10] = {
+ "",
+ "/off duty ",
+ "/enroute ",
+ "/in service",
+ "/returning ",
+ "/committed ",
+ "/special ",
+ "/priority ",
+ "/emergency ",
+ "/custom 1 " };
+
+static void xmit_object_report (int i, double c_lat, double c_long, int ambiguity, double c_offs)
+{
+ char object_name[20];
+ char object_info[50];
+ char info_comment[100];
+ double olat, olong;
+ char stemp[200];
+ packet_t pp;
+ unsigned char fbuf[AX25_MAX_PACKET_LEN];
+ int flen;
+
+
+ assert (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<MAX_TT_USERS; i++) {
+ if (tt_user[i].callsign[0] != '\0') {
+ printf ("%-6s %c%c %-3s %6d %d %+6d %d %6.2f %7.2f %-10s %c %s\n",
+ tt_user[i].callsign,
+ tt_user[i].overlay,
+ tt_user[i].symbol,
+ tt_user[i].digit_suffix,
+ (int)(tt_user[i].last_heard - now),
+ tt_user[i].xmits,
+ (int)(tt_user[i].next_xmit - now),
+ tt_user[i].corral_slot,
+ tt_user[i].latitude,
+ tt_user[i].longitude,
+ tt_user[i].freq,
+ tt_user[i].mic_e,
+ tt_user[i].comment);
+ }
+ }
+
+}
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: main
+ *
+ * Purpose: Quick test for some functions in this file.
+ *
+ * Description: Just a smattering, not an organized test.
+ *
+ * $ rm a.exe ; gcc -DTT_MAIN -Iregex tt_user.c tt_text.c encode_aprs.c latlong.c ; ./a.exe
+ *
+ *----------------------------------------------------------------*/
+
+
+#if TT_MAIN
+
+void text_color_set ( enum dw_color_e c )
+{
+ return;
+}
+
+int main (int argc, char *argv[])
+{
+ int n;
+
+ tt_user_init(NULL);
+
+ tt_user_heard ("TEST1", 12, 'J', 'A', G_UNKNOWN, G_UNKNOWN, "", "", ' ', "!T99!");
+ SLEEP_SEC (1);
+ tt_user_heard ("TEST2", 12, 'J', 'A', G_UNKNOWN, G_UNKNOWN, "", "", ' ', "!T99!");
+ SLEEP_SEC (1);
+ tt_user_heard ("TEST3", 12, 'J', 'A', G_UNKNOWN, G_UNKNOWN, "", "", ' ', "!T99!");
+ SLEEP_SEC (1);
+ tt_user_heard ("TEST4", 12, 'J', 'A', G_UNKNOWN, G_UNKNOWN, "", "", ' ', "!T99!");
+ SLEEP_SEC (1);
+ tt_user_heard ("WB2OSZ", 12, 'J', 'A', G_UNKNOWN, G_UNKNOWN, "", "", ' ', "!T99!");
+ tt_user_heard ("K2H", 12, 'J', 'A', G_UNKNOWN, G_UNKNOWN, "", "", ' ', "!T99!");
+ tt_user_dump ();
+
+ tt_user_heard ("679", 12, 'J', 'A', 37.25, -71.75, " ", " ", ' ', "!T99!");
+ tt_user_heard ("WB2OSZ", 12, 'J', 'A', G_UNKNOWN, G_UNKNOWN, "146.520MHz", "", ' ', "!T99!");
+ tt_user_heard ("679", 12, 'J', 'A', G_UNKNOWN, G_UNKNOWN, "", "Hello, world", '9', "!T99!");
+ tt_user_dump ();
+
+ for (n=0; n<30; n++) {
+ SLEEP_SEC(1);
+ tt_user_background ();
+ }
+
+ return(0);
+
+} /* end main */
+
+#endif /* unit test */
+
+
+/* end tt-user.c */
+
diff --git a/tt_user.h b/tt_user.h
new file mode 100755
index 0000000..bcca00b
--- /dev/null
+++ b/tt_user.h
@@ -0,0 +1,10 @@
+
+/* tt_user.h */
+
+
+void tt_user_init (struct tt_config_s *p);
+
+int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, double latitude,
+ double longitude, char *freq, char *comment, char mic_e, char *dao);
+
+void tt_user_background (void);
\ No newline at end of file
diff --git a/tune.h b/tune.h
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/tune.h
@@ -0,0 +1 @@
+
diff --git a/udp_test.c b/udp_test.c
new file mode 100755
index 0000000..68ca09e
--- /dev/null
+++ b/udp_test.c
@@ -0,0 +1,407 @@
+
+//
+// This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+// Copyright (C) 2011,2012,2013 John Langner, WB2OSZ
+// Contributed by Fabrice Faure for UDP part
+//
+// 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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*-------------------------------------------------------------------
+ *
+ * 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 <stdio.h>
+#include <unistd.h>
+//#include <fcntl.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <time.h>
+
+#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 <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <errno.h>
+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 100755
index 0000000..609f400
--- /dev/null
+++ b/utm/LatLong-UTMconversion.c
@@ -0,0 +1,190 @@
+//LatLong- UTM conversion.c
+//Lat Long - UTM, UTM - Lat Long conversions
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#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 at 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 at 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 at 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 at 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 100755
index 0000000..8c0fbbb
--- /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 <string.h>
+
+#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 100755
index 0000000..0f7a041
--- /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 100755
index 0000000..3bc72fa
--- /dev/null
+++ b/utm/SwissGrid.cpp
@@ -0,0 +1,140 @@
+
+#include <math.h>
+#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 at 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 100755
index 0000000..1230e4f
--- /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 <iostream.h>
+#include <iomanip.h>
+#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 <<endl;
+
+ LLtoUTM(RefEllipsoid, Lat, Long, UTMNorthing, UTMEasting, UTMZone);
+ cout << setiosflags(ios::showpoint | ios::fixed) << setprecision(5);
+ cout << "Calculated UTM position(Northing, Easting, Zone): ";
+ cout << UTMNorthing << " " << UTMEasting;
+ cout << " " << UTMZone <<endl;
+
+ UTMtoLL(RefEllipsoid, UTMNorthing, UTMEasting, UTMZone, Lat, Long);
+ cout << "Calculated Lat, Long position(Lat, Long): " << Lat << " " << Long << endl <<endl;
+
+ LLtoSwissGrid(Lat, Long, SwissNorthing, SwissEasting);
+ cout << setiosflags(ios::showpoint | ios::fixed) << setprecision(5);
+ cout << "Calculated Swiss Grid position(Northing, Easting): ";
+ cout << SwissNorthing << " " << SwissEasting << endl;
+}
+
+
+/* N 47.38195� E 8.54879� (Swiss Grid: 683.748 248.342)
+ N 47�12.625' / E 7� 27.103'= N 47.21041667 E 7.45171667(Swiss Grid = 600920/228685)
+ N 47�22.690' / E 8� 13.950'= N 47.37816667 E 8.23250000 (Swiss Grid = 659879/247637)
+*/
diff --git a/utm/constants.h b/utm/constants.h
new file mode 100755
index 0000000..35737e5
--- /dev/null
+++ b/utm/constants.h
@@ -0,0 +1,41 @@
+//constants.h
+
+#ifndef CONSTANTS_H
+#define CONSTANTS_H
+
+#include "LatLong-UTMconversion.h"
+
+#define PI M_PI
+#define FOURTHPI (PI / 4)
+#define deg2rad (PI / 180)
+#define rad2deg (180.0 / PI)
+
+static const Ellipsoid ellipsoid[] =
+{// id, Ellipsoid name, Equatorial Radius, square of eccentricity
+ { -1, "Placeholder", 0, 0 },//placeholder only, To allow array indices to match id numbers
+ { 1, "Airy", 6377563, 0.00667054 },
+ { 2, "Australian National", 6378160, 0.006694542 },
+ { 3, "Bessel 1841", 6377397, 0.006674372 },
+ { 4, "Bessel 1841 (Nambia) ", 6377484, 0.006674372 },
+ { 5, "Clarke 1866", 6378206, 0.006768658 },
+ { 6, "Clarke 1880", 6378249, 0.006803511 },
+ { 7, "Everest", 6377276, 0.006637847 },
+ { 8, "Fischer 1960 (Mercury) ", 6378166, 0.006693422 },
+ { 9, "Fischer 1968", 6378150, 0.006693422 },
+ { 10, "GRS 1967", 6378160, 0.006694605 },
+ { 11, "GRS 1980", 6378137, 0.00669438 },
+ { 12, "Helmert 1906", 6378200, 0.006693422 },
+ { 13, "Hough", 6378270, 0.00672267 },
+ { 14, "International", 6378388, 0.00672267 },
+ { 15, "Krassovsky", 6378245, 0.006693422 },
+ { 16, "Modified Airy", 6377340, 0.00667054 },
+ { 17, "Modified Everest", 6377304, 0.006637847 },
+ { 18, "Modified Fischer 1960", 6378155, 0.006693422 },
+ { 19, "South American 1969", 6378160, 0.006694542 },
+ { 20, "WGS 60", 6378165, 0.006693422 },
+ { 21, "WGS 66", 6378145, 0.006694542 },
+ { 22, "WGS-72", 6378135, 0.006694318 },
+ { 23, "WGS-84", 6378137, 0.00669438 }
+};
+
+#endif
diff --git a/utm2ll.c b/utm2ll.c
new file mode 100755
index 0000000..7e68c7c
--- /dev/null
+++ b/utm2ll.c
@@ -0,0 +1,72 @@
+/* UTM to Latitude / Longitude conversion */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#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 100755
index 0000000..f9cc894
--- /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 100755
index 0000000..d4222e0
--- /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 <http://www.gnu.org/licenses/>.
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * 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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <math.h>
+
+//#include <sys/time.h>
+#include <time.h>
+
+#if __WIN32__
+#include <windows.h>
+#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; j<MAX_CHANS; j++) {
+ xmit_bits_per_sec[j] = p_modem->baud[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<TQ_NUM_PRIO; p++) {
+
+ for (c=0; c<xmit_num_channels; c++) {
+
+ 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
+ if (pp != NULL) {
+
+ maxframe = (p == TQ_PRIO_0_HI) ? 1 : 7;
+/*
+ * Wait for the channel to be clear.
+ * For the high priority queue, begin transmitting immediately.
+ * For the low priority queue, wait a random amount of time, in hopes
+ * of minimizing collisions.
+ */
+ ok = wait_for_clear_channel (c, (p==TQ_PRIO_0_HI), xmit_slottime[c], xmit_persist[c]);
+
+ if (ok) {
+
+/*
+ * Channel is clear.
+ * Turn on transmitter.
+ * Start sending leading flag bytes.
+ */
+ time_ptt = dtime_now ();
+ ptt_set (c, 1);
+
+ pre_flags = MS_TO_BITS(xmit_txdelay[c] * 10, c) / 8;
+ num_bits = hdlc_send_flags (c, pre_flags, 0);
+
+/*
+ * Print trasmitted packet. Prefix by channel and priority.
+ */
+ 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 = 1;
+ ax25_delete (pp);
+
+/*
+ * Additional packets if available and not exceeding max.
+ */
+
+ while (numframe < maxframe && tq_count (c,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 100755
index 0000000..7b98ddb
--- /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 */
+
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-hamradio/direwolf.git
More information about the pkg-hamradio-commits
mailing list