diff --git a/.gitignore b/.gitignore index ad00755..b52d830 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,17 @@ *.o *.d +*.gch +.vscode +qn.cfg +qndtmf +gwys.txt +qnitap qndvap qndvrptr qnlink qngateway +qnigateway qnremote qnvoice qnrelay +qnmodem diff --git a/ACKNOWLEDGEMENTS b/ACKNOWLEDGEMENTS index 686dcff..d57b186 100644 --- a/ACKNOWLEDGEMENTS +++ b/ACKNOWLEDGEMENTS @@ -1,11 +1,17 @@ -QnetGateway started as g2_ircddb written by Scott Lawson, KI4LKF. Many of Scott's original comments are contained -in the this package. +QnetGateway started as g2_ircddb written by Scott Lawson, KI4LKF. -QnetGateway uses IRCDDB software written by Michael Dirska, DL1BFF. Both Scott and Michael published their code under -version 2 of the GNU General Public License. The current form of QnetGateway would be completely impossible without -Scott and Michael's contribution. Thank you for a great starting point of this current project! +QnetGateway uses IRCDDB software written by Michael Dirska, DL1BFF. Both Scott and +Michael published their code under version 2 of the GNU General Public License. The +current form of QnetGateway would be completely impossible without Scott and +Michael's contribution. Thank you for a great starting point of this current +project! -QnetGateway continues to be published under Version 2 of the GNU General Public License, see the LICENSE file. +Some parts of some QnetGateway programs are also inspired by ircDDBGateway, +DStarRepeater and MMDVMHost by Jonathan Naylor G4KLX, and his copyright appears in +those files that used his ideas and in some cases, use his source code. + +QnetGateway continues to be published under Version 2 of the GNU General Public +License, see the LICENSE file. Tom n7tae (at) arrl (dot) net diff --git a/BUILDING b/BUILDING deleted file mode 100644 index 2e8ccfb..0000000 --- a/BUILDING +++ /dev/null @@ -1,138 +0,0 @@ -Creating a DVAP or a Version 1 DVRPTR hotspot based on a Raspberry Pi or a BeagleBone -Black that can connect to both DStar reflectors as well as XREF reflectors based on -QnetGateway software is easy. - -Start with a Raspberry Pi with the latest Raspbian image (see http://raspberrypi.org) -or most any other computer with the latest Debian-based image. For this latest version -of QnetGateway requires the c++ compiler of at least version 4.9. - -Even if you are building an MMDVMHost-based hot-spot, the executables and their -source code are very small, so you can get away with a system with minimum storage -capacity. We have build gui-less versions on two gigabyte partitions! - -On the RPi, do "sudo raspi-config" and expand the partition, change the password -for the 'pi' user and do any other configuration setup. You don't need to overclock -the RPi for QnetGateway, the default clock rate is just fine. - -If you are using a DVAP Dongle or a DVRPTR_V1, login and plug in your device to see -if the OS is recognizing it. The kernel should auto load drivers and you will see -that with the "lsusb" command. The DVAP uses a FTDI chip and the DVRPTR uses Atmel. -If you don't see an approprite output from "lsusb" after your device is plugged in, -you need to enable it by executing: - -sudo depmod -sudo modprobe - -where YOURDEVICEMODULE is "ftdi_sio" for the DVAP or is "cdc_acm" for the DVRPTR. -After a reboot you should see the device in the "lsusb" list. If you don't see -the device listed, QnetGateway software will never be able to open it either. - -You will need several packages to build the QnetGateway gateway. You will probably -already have all or most of these but it still doesn't hurt to be sure: - -sudo apt-get update -sudo apt-get upgrade -sudo apt-get install make g++ unzip git libconfig++-dev - -If you are building a QnetGateway + MMDVMHost system, please use the instructions -in the MMDVM.README file. If you are building a QnetGateway for a DVAP or a -DVRPTR_V1 read on... - -git clone git://github.com/n7tae/QnetGateway.git - -This will create a QnetGateway directory with everything you need to build a dvap or -dvrptr ircddb gateway. - -The first thing to do is change to the build directory with "cd QnetGateway" and then -type "make" to build all the QnetGateway executables. If you need DTMFS then also -execute "make g2link_test". - -Next, create your qn.cfg configuration file. There are two example for you to look -at: -. qn.everything.cfg contains all parameter with lengthly comments about what - each parameter does. The definitions that are commented out are defined with - their default value. -. qn.dvap.cfg is the simplest possible configuration for a 2m DVAP. If you have - a 70cm DVAP rename the module to "b" and change the frequency. - -Remeber the everything file contain detailed comments about all of the values you -can set. Just read through it and edit accordingly. In the end you will need -a configuration file called "qn.cfg". - -Additional information about the configuration as well as other important and -useful features are also in the CONFIGURING file. - -The gwys.txt file is the internet address and port numbers for any gateway you -would like your ircddb gateway to be able to connect to. The one delivered with -this package is special: It has only DCS reflectors, X-reflectors and DStar -reflectors and the X-reflectors are configured with the 20001 port instead of the -default 30001 prot. This will allow you to connect to these XREF reflectors without -creating any port-forwarding rules on your home router. You will also want to move -X-reflectors to port 20001 if you are going to tether you device via WiFi to a -smart-phone for internet access. Most phone companies will not let you configure -port-forwarding rules on you phone internet account. If you operate behing a -router/firewall without port forwarding in place, you will not be able to -do most callsign routing techniques discussed in CONFIGURING.txt, but you should -still be able to connect to reflectors. You will be able to route to the new -smart-group-server if you are mobile. They have solved the "mobile routing -problem"! - -There are MANY OTHER gateways to which you can connect. Executing get_gwys_list.sh -will download a HUGE list of reflectors and gateways from www.va3uv.com with port -address that may need port-forwarding to your sytem. I have provided anotherscript, -'get_reflectors.sh' that will download the same list from va3uv.com, but filter it -so that it only contains DCS x-reflectors (DCSXXX), DStar reflectors (REFXXX) and -X-reflectors (XRFXXX) and it will put all x-reflectors on port 20001 so you -probably won't need any port-forwarding on your home router. - -There is another script, reflist.sh, that will download REF, XRF and DCS reflectors -from another source. This is probably the preferred method to getting a gwys.txt -file. - -Based on the above discussion, execute either "./reflist.sh", "./get_reflectors.sh" or -"./get_gwy_list.sh". - -If you plan on using DTMFS, you can also edit proc_qnlinktest to add new -dtmfs commands. - -Then install your system. you have two choices, either DVAP or DVRPTR_V1 by -typing "sudo make installdvap" or "sudo make installdvrptr", respectively. -This should get you up and running. It will take a few minutes for QnetGateway -to get fully connected to the IRCDDB network. - -Finally, if you want/need DTMFS, type "sudo make installdtmfs". - -The service scripts in /lib/systemd/system and everything else in /usr/local: -The executables will be in /usr/local/bin and the qn.cfg file and other data -will be in /usr/local/etc. - -If you find that you need to modify the configuration file, remember that the -installed systems read the /usr/local/etc/qn.cfg file, NOT THE ONE IN YOUR -BUILD DIRECTORY. To keep them synced, it is good admin practice to modify the -qn.cfg file in your build directory and then do a "sudo make uninstall" followed by an "sudo make install. - -If you are having trouble connecting, use journalctl to view the log output -of each process. "sudo journalctl -u will show the log from that -service. The QnetGateway service is qngateway, QnetLink is qnlink, QnetDVAP is -qndvap and QnetDVRPTR is qndvrptr. - -The beginning of each log file will report the values of all the configuration -parameters (even the ones you didn't specify in qm.cfg) and after that -you will see the verbose reports of what each service is doing. These logs are -invaluable for traking down problems with your qn.cfg file. You can see in real -time what is being added to the logs during operation appending "-f" to the -journalctl command. - -You can clean up the build directory of intermediate *.o files with "make clean" -or, you can remove the intermediate *.o files and binary executables with "make -realclean". Note that "make realclean" only removes the binary files from your -build directory and not the copies you installed into /usr/local/bin with the -"sudo make install..." command. - -If you want to uninstall everything return to the build directory and type either -"sudo make unistalldvap" or "sudo make uninstalldvrptr" and possibly "sudo make -uninstalldtmfs". This will shutdown the services and remove the service scripts -and everything from /usr/local. - -Tom Early, n7tae (at) arrl (dot) net diff --git a/CONFIG+INSTALL b/CONFIG+INSTALL new file mode 100644 index 0000000..ba3db38 --- /dev/null +++ b/CONFIG+INSTALL @@ -0,0 +1,168 @@ +Creating a hotspot based on a Raspberry Pi or a BeagleBone Black that can connect to +XRF and DCS reflectors as well as XREF reflectors based on QnetGateway software is +easy. + +Start with a Raspberry Pi with the latest Raspbian image (see http://raspberrypi.org) +or most any other computer with the latest Debian-based image. For this latest version +of QnetGateway requires the c++ compiler of at least version 4.9. + +Even if you are building an MMDVMHost-based hot-spot, the executables and their +source code are very small, so you can get away with a system with minimum storage +capacity. We have build gui-less versions on two gigabyte partitions! You don't need +to overclock the RPi for QnetGateway, the default clock rate is just fine. + +If you are using a DVAP Dongle, a DVRPTR_V1, an MMDVM modem or connecting to an Icom +Terminal and Access Point enabled radio, login and plug in your device to see if the OS +is recognizing it. The MMDVM modem will either plug right onto the Raspberry Pi header, +or with an appropriate adaptor, it can be plugged into a USB port. The kernel should +auto load drivers and you will see that with the "lsusb" command. The DVAP and the Icom +radio digital-to-serial cable uses a FTDI chip and the DVRPTR uses Atmel. If you don't +see an appropriate output from "lsusb" after your device is plugged in, you need to +enable it by executing: + +sudo depmod +sudo modprobe + +where YOURDEVICEMODULE is "ftdi_sio" for the DVAP or is "cdc_acm" for the DVRPTR. +After a reboot you should see the device in the "lsusb" list. If you don't see +the device listed, QnetGateway software will never be able to open it either. + +You will need several packages to build the QnetGateway gateway. You will probably +already have all or most of these but it still doesn't hurt to be sure: + +sudo apt-get update +sudo apt-get upgrade +sudo apt-get install make g++ unzip git + +If you are building a QnetGateway + MMDVMHost system, please use the instructions +in the MMDVM.README file in order to setup and start your MMDVMHost. + +If you are building a QnetGateway for an Icom repeater, you'll need a different +git repository. Please look for QnetICOMGateway. + +If you are building an MMDVM, DVAP, DVRPTR_V1, using an MMDVM modem but don't need +any mode besides D-Star or you are using an ICOM Access Point or Terminal Mode +gateway, start by cloning this repository. + +git clone git://github.com/n7tae/QnetGateway.git + +This will create a QnetGateway directory with everything you need to build your +Gateway system. + +The first thing to do is change to the build directory with "cd QnetGateway" and then +define and create your configuration file, qn.cfg: + +./qnconfig + +This will start a menu driven script. In this mode, only the most common, useful +parameters are available. If you want access to absolutely every parameter use: + +./qnconfig expert + +Please be sure you know what you are doing if you change these normally hidden parameters. +The configuration script will show you the default value for a parameter. For most cases +the default value is fine, so your qn.cfg file will usually be rather small. + +At a minimum, you need to specify an IRCDDB login (please use your legal callsign) and +at least one module. By convention, specify a 23cm module on A, a 70cm module on B and +a 2M module on C. QnetGateway supports a maximum of three modules. + +The configure script will show you the default values of every parameter the +QnetGateway programs use. In most cases, the defaults are just fine and you don't +need to override them with your own values. If you need to override the default value, +the general method is to type the key followed by the new value. If you want to delete +an overridden value, type a "u" followed by the key you want to unset. Boolean values +(true or false) can be toggled by just entering the key. + +By default, QnetGateway will only connect to the IPv4 QuadNet server at rr.openquad.net. +QnetGateway is capabile of dual-stack operation and can also connect to the IPv6 server +at rrv6.openquad.net. If you want to operate in dual stack mode, enter the IRC sub-menu +and set ha to "rrv6.openquad.net" and hb to "rr.openquad.net". + +Once your operating in dual-stack mode, any routing will prefer an IPv6 address, if +available. It's that easy. + +For a DVAP Dongle or a DVRPTR V1, there are some parameters that must be specified, +like the serial number. The two "inverse" parameters for the DVRPTR will probably +have to change. If you don't hear traffic on a connected system, try toggling the +dvrptr_inverse_rx parameter. If you aren't heard, toggle the dvrptr_inverse_tx +parameter. For the DVAP Dongle, or if you are using an MMDVM modem without +MMDVMHost, you need to specify the operational frequency. You also need to make +sure the device address is correct for the MMDVM modem. "Hats" connected to the +Raspberry Pi header can usually be found at /dev/ttyAMA0 while device plugged into +a USB port are usually at /dev/ttyUSB0. + +If you want to use the closed-source, legacy D-Plus reflectors and repeaters, you +need to make sure you are authorized to use those systems. They require that you +are a registered user, see www.dstargateway.org for more information. If you are +a registered user, you can enable QnetGateway to used this closed-source system +by DPlus. By default, when QnetLink registers your callsign you will download +both repeaters and reflectors. You can control this with configuration parameters. +You can also control the priority of D-Plus reflectors and repeaters. By default, +QnetLink first loads the gwys.txt file first and then the systems delivered by the +D-Plus Authorization system. You can change the order, if you want definitions +in your gwys.txt file to override any delivered by the D-Plus authorization server. + +The information downloaded from the DPlus server is dynamic and will change +from hour to hour. You can update QnetLink by sending " F" to your system. +This will purge the current table and re-authorize with the DPlus server and +then reload gwys.txt. + +Because of the way DPlus authorization works, QnetLink can't actually confirm +that your authorization was successful. If your system is unlinked after trying +to transmit into a DPlus system, it means that your authorization was +unsuccessful. This might indicate that their may be a problem with your +DPlus registration. + +If you want to see what's going on inside the programs, there are logging options +available that will cause a lot more log entries. These extra entries can be +useful if you are trying to track down problems with your system. + +After you are happy with your configuration, be sure to write it out with the 'w' +key in the main menu. It will show you your qn.cfg file after it writes it out. +After you install and try out your system, you may find that you need to change +some configuration values. In that case just start the configure script again. +It will read the current qn.cfg file when it start and initialize the menus +accordingly. + +Of course, you can always build your own qn.cfg file. There are three example for +you to look at: +. qn.everything.cfg contains all parameter with comments about what + each parameter does. The definitions that are commented out are defined with + their default value. +. qn.dvap.cfg is the simplest possible configuration for a 2m DVAP. If you have + a 70cm DVAP rename the module to "b" and change the frequency. +. qn.mmdvm.cfg is the starting place for configuring an MMDVMHost repeater. Be sure + the module assignment agrees with the module configured in your MMDVM.cfg file. +. qn.itap.cfg is a simple configuration file for Icom's Terminal and Access Point + Mode. Please read ITAP.README for more information. + +Once you have your qn.cfg file, your ready to compile and install your system, type: + +./qnadmin + +The first thing you want to do is to create your gwys.txt file. use the 'gw' key +to get into that sub-menu. There are several choices to initialize your gwys.txt +file. Choose one and then you can edit the file to your satisfaction. + +You are now ready to install your QnetGateway system. If you are installing an +MMDVM-based system, follow the instructions in MMDVM.README to get MMDVMHost +up and running. + +Now, you can compile and install your system with the 'is' key. qnadmin will +use your qn.cfg file to figure out what needs to be compiled and how it is to +be installed. + +If you plan on using DTMF, use the 'id' key to install it. Once you install +something, the qnadmin will dynamically change and show you how to uninstall the +installed items. + +The maintenance sub-menu accessed with the 'm' key will let you stop and start +different programs in your installed package. Note that this just uses +systemctl to start and stop a service. It will not uninstall the service. +You might want to do this if you have changed your configuration file. + +The log sub-menu accessed with the 'l' key can be use to put a "tail" on different +log files and you can watch a log file in real-time. + +Tom Early, n7tae (at) arrl (dot) net diff --git a/CONFIGURING b/CONFIGURING deleted file mode 100644 index a9e4f20..0000000 --- a/CONFIGURING +++ /dev/null @@ -1,400 +0,0 @@ -/* - * Copyright (C) 2010, 2011, 2012 by Scott Lawson KI4LKF - * addition Copyright (C) 2018 by Thomas A. Early N7TAE - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -I have substantially modified (and hopefully simplified) the configuration -of this software, now, modules QnetGateway, QnetLink, QnetDVAP and QnetDVRPTR -all use a single configuration file. Further, and this is the exciting -part, NEARLY ALL of the parameters these modules need have a useful -default value. If the default value is acceptible to you (and it -probably will be) then you only need to specify what you need to change. - - -This means that for most users, you will only need to specify a few -parameters. For example, if you want to set up a 70cm dvap, your working -configuration file might be: - -------------------------------------------------------------------- -# my configuration, using rr.openquad.net with my DVAP Dongle -# on startup link module b to REF020C - -ircddb = { - login = "XX0XXX" -} - -module = { - b = { - type = "dvap" - frequency = 445.474 - serial_number = "AP123456" - } -} - -link = { - link_at_start = "CREF020A" - admin = [ "XX0XXX"] -} - -------------------------------------------------------------------- - -Of course, you can add other parameters, such as latitude and longitude, -or a URL that will show up on the "User Gateways" page of -www.openquad.net. This software is highly flexible, so you can have -different modules running on different computers and these hidden -configuration parameters to allow that are there, waiting to be defined. -However, most hams won't have to bother with them! - -Some other features are discussed below and are pretty much directly -quoted from KI4LKF original documentation. - -Tom Early, n7tae (at) arrl (dot) net - - -G2_ircDDB adapted from the OpenG2 - - DESCRIPTION -================= - -QnetGateway is a Dstar G2 gateway for the Dstar network with ircDDB routing -and USroot routing. It runs on Linux(as a Linux service). -ircddb originated from the OpenG2 with only one change. - Instead of using the local Postgres database server, - we use the remote IRC database server.(group2 or group1) - So, the difference between OpenG2 and ircddb is 0.01% -This new software QnetGateway has been approved for use on the ircDDB network. - -IRC Gateways such as g2_ircdb (other IRC gateways copied from our OpenG2) -connect to group2 server(USA/Canada) or group1 server(Europe). -What are these group1, group2 servers? Group1 and group2 servers are really -the combination of 2 programs running together: - -These are: ----- An IRC (Internet Relay Chat) program, in this case it is the inspIRCd - program ----- the LDAP (Light Directory Access Protocol) program - -So, ircDDB database servers are NOTHING new, they were designed by groups -of people 20-30 years ago, before even the ICOM Dstar came out. -The German IRC team, copied the above programs (IRC, LDAP) from those groups, -and they called it group1 and group2. - -The reason for that was that an IRC(Internet Relay Chat) server, sends data -immediately, while the ICOM dstar network sends data 5 minutes later after a -user keyed up a Dstar repeater. So, IRC is faster, but it overloads your -home-network. - -Using a PERSONAL callsign to set up an ircDBB gateway -===================================================== -In qn.cfg, set OWNER equal to your personal callsign. -In QnetLink.cfg, set OWNER equal to your personal callsign. -In your repeater config file, set OWNER equal to your personal callsign. - -Using a REPEATER callsign to set up an ircDBB gateway -===================================================== -In QnetGateway.cfg, set OWNER equal to a REPEATER callsign (that you received -from the ham authority of your Country). -In QnetLink.cfg, set OWNER equal to a REPEATER callsign (that you received -from the ham authority of your Country) In your repeater config file, set -OWNER equal to a REPEATER callsign (that you received from the ham authority -of your Country). - -However, since the IRC database servers(group2 in Canada/USA, group1 in -Europe) do NOT allow personal callsigns to be used as gateways, then you may -be asking what is the point? - -Here is the logic: -In some Countries, it is very difficult to receive a GATEWAY (club/group) -callsign, sometimes it takes years. In cases such as these, hams only have -their own personal callsign to use. So, how do we get around the problem when -the IRC servers group1, group2 require a GATEWAY callsign, but the user only -has a PERSONAL callsign? - -Some groups of hams got together and installed their own IRC database server. -An IRC database server (like group1, group2) is really these 2 basic programs: - ----- inspIRCd server software which is an IRC server (Internet Relay Chat) - which was designed in 1988. One such IRC program is inspIRCd which was - chosen by the German IRC team for use in their group1, group2 installations. - ---- LDAP server software, which is the Light Directory Access Protocol server, - that was written in 1993. - -These 2 programs inspIRCd + LDAP make up the the IRC database servers as we know -them today and were installed together by the IRC team on group1, group2 -installations. - -That is all there is to it. - -When LDAP runs, it checks the Gateway password as listed in QnetGateway.cfg: -IRC_PASS=... When LDAP does NOT run, then the IRC_PASS is NOT checked. - -So, some groups of hams, have installed the inspIRCd server, without installing -the LDAP server. In such an installation, then the IRC_PASS is NOT checked, so -you can use your personal callsign. - -g2_ircDDB supports the following commands in YRCALL - -Note: In the commands that folow, _ is a SPACE. - -1) -For Echotest/playback. -YRCALL=_ _ _ _ _ _ _E - -2) -For Voice Mail: -YRCALL=_ _ _ _ _ _ S0 -The above command will Store/create voice mail in the dvtool file -x_voicemail.dat. -YRCALL=_ _ _ _ _ _ R0 -The above command will Recall/playback voice mail from the dvtool file -x_voicemail.dat. -YRCALL=_ _ _ _ _ _ C0 -The above command will Clear/delete voice mail. File x_voicemail.dat will be -deleted. In all cases, the letter x in the file name x_voicemail is the -module A,B or C. - -3) -For inquiring the status of the link: -YRCALL=_ _ _ _ _ _ _I - -4) -For unlinking: -YRCALL=_ _ _ _ _ _ _U - -5) -For linking: -YRCALL=XXNYYYML -Where XXNYYY is a friendly gateway, M is the gateways's module and L is the -LINK command. -YRCALL=XRFNNNML -Where XRFNNN is a friendly reflector, M is the reflector's module and L is -the LINK command. - -Note about linking: -After linking succeeds, set YRCALL=CQCQCQ because your audio will go to the -remote reflector ONLY if YRCALL=CQCQCQ. - -6) -For executing scripts: -YRCALL=_ _ _ _ _ _ nX -where n can be from 0-9 or A-Z. -Example: YRCALL=_ _ _ _ _ _1X -Then the script exec_1.sh will be executed. -Two scripts, exec_R.sh and exec_H.sh are included to reboot and halt your -system, respectively. Also note that rpt1 is passed to these scripts\ -so you can use this as an input parameter for your scripts. -Only admins can execute scripts, so set QnetLink.admin to your callsign - -7) -Enabling and disabling INCOMING HotSpotNode connections: -To Enable: -YRCALL=_ _ _ _ _ _ D1 -To Disable: -YRCALL=_ _ _ _ _ _ D0 - -Required software to run the QnetGateway gateway correctly: ---- QnetGateway: The G2 audio gateway. ---- QnetLink: This communicates with QnetGateway to link the local G2 gateway - to reflectors. Note: QnetLink is NOT required if you only make - routing calls or talk locally on the repeater. ---- rptr: This is our dstar repeater software that uses a GMSK adapter/modem. - Instead of rptr, you can use our QnetDVAP dstar repeater software - which uses a DVAP device. Intead of rptr, you can use our QnetDVRPTR - dstar repeater software which uses the DV-RPTR modem(dg1ht). - -ROUTING methods -=============== -Some Dstar routing examples follow. Please do not use the same data because -KJ4NHF is one of our own Dstar repeaters, and KI4LKF is a personal callsign -in our group. - -Example of ZONE routing: - Lets say that your repeater is KJ4NHF, and you are currently on - your local repeater module B, your callsign is KI4LKF - and you want to reach remote gateway XXNYYY module C - In this case, your radio should be programmed like this: - MYCALL=KI4LKF - YRCALL=/XXNYYYC - RPT1=KJ4NHF B - RPT2=KJ4NHF G - -Example of CALLSIGN routing: - Lets say that your repeater is KJ4NHF, and you are currently on - your local repeater module B, your callsign is KI4LKF - and you want to talk to user XX0XXX - In this case, your radio should be programmed like this: - MYCALL=KI4LKF - YRCALL=XX0XXX - RPT1=KJ4NHF B - RPT2=KJ4NHF G - -Example of Cross-Band routing: - Lets say that your repeater is KJ4NHF, and you are currently on - your local repeater module B, your callsign is KI4LKF - and you want to talk from your local module B to your local module C - In this case, your radio should be programmed like this: - MYCALL=KI4LKF - YRCALL=CQCQCQ - RPT1=KJ4NHF B - RPT2=KJ4NHF C - -DTMF decoding and processing -============================= -Prepare the software to decode and process DTMF tones ------------------------------------------------------ -Edit the Shell script proc_qnlinktest.sh -Correct the value for G2 to be the local G2 gateway -callsign(same value in qn.cfg ----> OWNER) -Edit G2_INT_IP as follows: - If your QnetGateway.cfg, says "G2_INTERNAL_IP=0.0.0.0" then set - G2_INT_IP=127.0.0.1. - If your QnetGateway, says G2_INTERNAL_IP= - then set G2_INT_IP equal to the exact value of G2_INTERNAL_IP. -Edit G2_INT_PORT to be equal to G2_INTERNAL_PORT in QnetGateway.cfg. - -Note: When local RF user has entered dtmf tones on the Dstar HT - and then PTT is released, QnetGateway will print the whole - sequence in the QnetGateway.log just before it creates the - dtmf file under /tmp. - -How to enter DTMF tones correctly on your Dstar HT --------------------------------------------------- -If you want to have perfect DTMF decoding/processing in QnetGateway, -follow these suggestions: - - 1) Hold down each dtmf key for at least 150 milliseconds. - 2) Leave at least 250 milliseconds of "silence", when you go from - one dtmf key to the next dtmf key. - -If you use a DTMF autodialer on your Dstar radio, you can program -the above numbers easily into the ICOM's radio "auto-dialer". - -Note: MAXIMUM dtmf sequence is up to 32 dtmf tones. - -What dtmf tones are being decoded and processed ------------------------------------------------ -To link to an XRF reflector: Example: B02102 -That says: link to XRF021 module B. So, we use the # dtmf key -to mean that we're interested in linking to an XRF reflector. The -last two digits are the remote module 01 thru 05 (which is -translated to A thru E). - -To link to a DCS reflector: Example: D00126 -That says: link to DCS001 module Z. So, we use the D dtmf key -to mean that we're interested in linking to a DCS reflector. The -last two digits are the remote module 01 thru 26 (which is -translated to A thru Z). - -To link to REF: Example: *01601 -That says: link to REF016 module A. So, we use the * dtmf key -to mean that we're interested in linking to a REF reflector. The -last two digits are the remote module 01 thru 05 (which is -translated to A thru E). - -To unlink (from any reflector xrf or ref): # - -To get the "link status": 0 or 00 - -Note: -You can extend the shell script to do more things. like force -your repeater to ID itself. Any YRCALL command that can be executed -by g2link_test, can be added to the shell script. Basically, the -Linux shell script proc_QnetGateway_dtmfs.sh converts the decoded -dtmf tones into YRCALL commands using g2link_test program. - -=========== -QnetLink is a small program that is used to link a local RF repeater -band to a remote reflector. QnetLink software is used by our QnetGateway -(an IRCddb gateway) and by our g2_ccs (a CCS gateway). - -Before we begin, there are some dat files included in the QnetLink -ZIP package. These dat files are: - -already_linked.dat -already_unlinked.dat -failed_linked.dat -linked.dat -unlinked.dat -id.dat - -All of the above dat files are dvtool format files and they are used -when certain events take place. For example, when the link between your -gateway and a reflector is established, then the file "linked.dat" will -be played over RF, so anyone listening on your repeater, will hear the -announcement that the link has been established. You do not have to -change these files, unless you want to have your own personal voice -played over RF. These dat files were created using a computer and they -are NOT anyone's voice. - -The only dat file most hams will need to change is id.dat. The file -id.dat contains the audio "UNLINKED" and nothing else. When the gateway -is not linked and the RF user sets YRCALL=_______I to request the -status of the link, the file id.dat will be played back over RF. But -you should create your own id.dat file that should identify your own -repeater with extra information if you like. - -For example, you could create your own dvtool audio file that contains -the audio: "This is repeater ...". A simple way to create your own -recorded "repeater identification file" id.dat is to use your Dstar HT -and set YRCALL command: YRCALL=______S0 and key up your repeater. -Start talking, and the gateway will record your audio into the file -x_voicemail.dat where x is one of A.B or C. Now copy that file: -sudo cp -f /tmp/C_voicemail.dat /usr/local/etc/it.dat - -You may be thinking why did we put all this source code inside QnetLink -instead of putting it all inside the Gateway software QnetGateway (or -g2_ccs CCS gateway software). Because it is NOT a good design to burden -the G2 gateway with code that is not DSTAR protocol and reflectors are -NOT part of Dstar and they are not Dstar protocol. - -The only code that should be in a gateway, is G2 protocol code. This way -we keep the gateway (QnetGateway) clean of any unneccesary source -code logic and containing only pure G2/D-Star logic. - -QnetDVAP - -The serial number required in the .cfg file is obtained from the label -that can be seen on the DVAP circuit board. In most cases set the power -to maximum level. - -QnetDVRPTR - -The serial number required in the qn.cfg file can most easily be obtained -by examining the /var/log/QnetDVRPTR.log file once the board has been -powered up by the BBB or RasPi. Once you know the board serial number, -edit /usr/local/etc/g2.cfg. Please note that once installed, you -need to edit the configuration files in /usr/local/etc, not where you -build the software. You need to be root to edit files in /usr/local/etc. -After editing /usr/local/etc/g2.cfg file you can restart the effected -program with "sudo service QnetDVRPTR restart". Or you can alway just -reboot with "sudo reboot". - -Rig specific parameters are in "module.x.rf_rx_level", "module.x.inverse.rx" and -"module.x.inverse.tx". You need to play with these to work best with your rig. -With my Kenwood TM-V71, I use the default values. - -You can first start with inverse.rx, trying true or false. Use the echo command -on your radio and look in the logs to see if you are getting into the -dvprtr. Remember to restart the QnetDVRPTR service anytime you modify -/usr/local/etc/g2.cfg. Once you are able to get into the QnetDVRPTR, -then you can work out the inverse.tx paramter. Once you hear anything -with in the echo mode, move rf_rx_level up or down to get the best -audio. - -Once you have a working system, it's a good idea to back up you qn.cfg -files. diff --git a/DPlusAuthenticator.cpp b/DPlusAuthenticator.cpp new file mode 100644 index 0000000..734af7e --- /dev/null +++ b/DPlusAuthenticator.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2010-2015 by Jonathan Naylor G4KLX + * Copyright (C) 2018-2019 by Thomas A. Early N7TAE + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DPlusAuthenticator.h" + +CDPlusAuthenticator::CDPlusAuthenticator(const std::string &loginCallsign, const std::string &address) : +m_loginCallsign(loginCallsign), +m_address(address) +{ + assert(loginCallsign.size()); + + Trim(m_loginCallsign); +} + +CDPlusAuthenticator::~CDPlusAuthenticator() +{ +} + +bool CDPlusAuthenticator::Process(std::map &gwy_map, const bool reflectors, const bool repeaters) +// return true if everything went okay +{ + int result = client.Open(m_address, AF_UNSPEC, "20001"); + if (result) { + fprintf(stderr, "DPlus Authorization failed: %s\n", gai_strerror(result)); + return true; + } + return authenticate(gwy_map, reflectors, repeaters); +} + +bool CDPlusAuthenticator::authenticate(std::map &gwy_map, const bool reflectors, const bool repeaters) +{ + unsigned char* buffer = new unsigned char[4096U]; + ::memset(buffer, ' ', 56U); + + buffer[0U] = 0x38U; + buffer[1U] = 0xC0U; + buffer[2U] = 0x01U; + buffer[3U] = 0x00U; + + ::memcpy(buffer+4, m_loginCallsign.c_str(), m_loginCallsign.size()); + ::memcpy(buffer+12, "DV019999", 8); + ::memcpy(buffer+28, "W7IB2", 5); + ::memcpy(buffer+40, "DHS0257", 7); + + if (client.Write(buffer, 56U)) { + fprintf(stderr, "ERROR: could not write opening phrase\n"); + client.Close(); + delete[] buffer; + return true; + } + + int ret = client.ReadExact(buffer, 2U); + size_t sofar = gwy_map.size(); + unsigned int returned = 0; + + while (ret == 2) { + unsigned int len = (buffer[1U] & 0x0FU) * 256U + buffer[0U]; + // Ensure that we get exactly len - 2U bytes from the TCP stream + ret = client.ReadExact(buffer + 2U, len - 2U); + if (0 > ret) { + fprintf(stderr, "Problem reading line, it returned %d\n", errno); + return true; + } + + if ((buffer[1U] & 0xC0U) != 0xC0U || buffer[2U] != 0x01U) { + fprintf(stderr, "Invalid packet received from 20001\n"); + return true; + } + + for (unsigned int i = 8U; (i + 25U) < len; i += 26U) { + std::string address((char *)(buffer + i)); + std::string name((char *)(buffer + i + 16U)); + + Trim(address); + Trim(name); + name.resize(8, ' '); + + // Get the active flag + bool active = (buffer[i + 25U] & 0x80U) == 0x80U; + + // An empty name or IP address or an inactive gateway/reflector is not added + if (address.size()>0U && name.size()>0U && active) { + returned++; + if (reflectors && 0==name.compare(0, 3, "REF")) + gwy_map[name] = address.append(" 20001"); + else if (repeaters && name.compare(0, 3, "REF")) + gwy_map[name] = address.append(" 20001"); + } + } + + ret = client.ReadExact(buffer, 2U); + } + + printf("Probably authorized DPlus on %s using callsign %s\n", m_address.c_str(), m_loginCallsign.c_str()); + printf("%s returned %u systems\n", m_address.c_str(), returned); + printf("The gateway map increased by %u additional DPlus gateways\n", (unsigned int)(gwy_map.size() - sofar)); + client.Close(); + + delete[] buffer; + + return false; +} + +void CDPlusAuthenticator::Trim(std::string &s) +{ + auto it = s.begin(); + while (it!=s.end() && isspace(*it)) + s.erase(it); + auto rit = s.rbegin(); + while (rit!=s.rend() && isspace(*rit)) { + s.resize(s.size() - 1); + rit = s.rbegin(); + } +} diff --git a/DPlusAuthenticator.h b/DPlusAuthenticator.h new file mode 100644 index 0000000..f350076 --- /dev/null +++ b/DPlusAuthenticator.h @@ -0,0 +1,40 @@ +#pragma once +/* + * Copyright (C) 2010-2013 by Jonathan Naylor G4KLX + * Copyright (C) 2018-2019 by Thomas A. Early N7TAE + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. +**/ + +#include +#include +#include +#include "TCPReaderWriterClient.h" + +class CDPlusAuthenticator { +public: + CDPlusAuthenticator(const std::string &loginCallsign, const std::string &address); + ~CDPlusAuthenticator(); + + bool Process(std::map &gwy_map, const bool reflectors, const bool repeaters); + +private: + std::string m_loginCallsign; + std::string m_address; + CTCPReaderWriterClient client; + + void Trim(std::string &s); + bool authenticate(std::map &gwy_map, const bool reflectors, const bool repeaters); +}; diff --git a/DTMF+REMOTE+VOICE.README b/DTMF+REMOTE+VOICE.README index 7e62400..1e7e52f 100644 --- a/DTMF+REMOTE+VOICE.README +++ b/DTMF+REMOTE+VOICE.README @@ -4,19 +4,12 @@ DTMF is available with the QnetGateway Software. You can do things like linking and unlinking from you radio's keyboard, if present. For example, typing "B75703" should link you to XRF757 C. -DTMF is not enabled by default. To enable it, first create a DTMF script called -"qndtmf" in the build directory. A complete, functional script is included -and is called "qndtmf.sh", so you can start by just copying it to the target: +DTMF is not enabled by default. You can install and uninstall it from the +qnadmin script. -cp qndtmf.sh qndtmf - -Then you can install DTMF: sudo make installdtmf - -To uninstall DTMF: sudo make uninstalldtmf - -Be sure to look at the script. It contains examples of all the DTMF commands it -supports. You can add more if you are good at shell programming and understand -how qnremote works. +Be sure to look at the 'dtmf' script. It contains examples of all the DTMF +commands it supports. You can add more if you are good at shell programming +and understand how qnremote works. ######## QnetRemote ######### @@ -54,32 +47,37 @@ to do this search the web with "linux cron job". ######## QnetVoice ######## -QnetVoice is another command line program to send any DVTool-formatted file -to a moule configured on you gateway. It has a simple interface, like +QnetVoice is another command line program to send any ambe-formatted file +to a module configured on you gateway. It has a simple interface, like QnetRemote. To get started, open a shell on your system and type "qnvoice": pi@raspberrypi:~ $ qnvoice -Usage: qnvoice +Usage: qnvoice Where... module is one of your modules mycall is your personal callsign - dvtoolFile is a dvtool file + dat_file is a .dat voice file file Please note that and are not case sensitive, but - is. + is. -So what's a DVTool file? All the voice prompts delivered with QnetGateway, -in the QnetGateway/announce directory and DVTool files. And, you can +So what's a .dat voice file? All the voice prompts delivered with QnetGateway, +in the QnetGateway/announce directory are .dat files. And, you can easily create your own! -To make your own DVTool file, just put " S0" in your radio's YourCall -and key up and talk. You will be making a "voice mail" DVTool file: -/tmp/X_voicemail.dat, where X is the module on which you are transmitting. -Once you have created it, you can move it out of the /tmp directory and -rename it. Then you can use it in a qnvoice command. You can also replace -all of the standard voice messsages with your own. If you want to do this -put your versions in a directory outside of the build directoy and make -a small script that will copy your messages over the existing, installed +To make your own .dat voice file file, just put " S0" in your radio's +YourCall and key up and talk. You will be making a "voice mail" .dat file: +/tmp/X_voicemail.dat2, where X is the module on which you are transmitting. +Once you have created a voicemail file, you can move it out of the /tmp +directory and rename it, but if you want to use it in a qnvoice command, +you need to strip off the first 56 bytes. You can do this with the command: + +tail -c +56 /tmp/X_voicemail.dat2 > mynewvoiceprompt.dat + +Then you can use it in a qnvoice command. You can also replace all of the +standard voice messages with your own. If you want to do this put your +versions in a directory outside of the build directory and make a small +script that will copy your messages over the existing, installed messages: #!/bin/bash @@ -89,4 +87,3 @@ sudo cp ./id.dat /usr/local/etc That way, when you pull down a new release and build and install it, you can quickly update the voice messages with your own. - diff --git a/DVAPDongle.cpp b/DVAPDongle.cpp index 6971ca8..74c1898 100644 --- a/DVAPDongle.cpp +++ b/DVAPDongle.cpp @@ -36,7 +36,7 @@ CDVAPDongle::~CDVAPDongle() { } -bool CDVAPDongle::Initialize(char *serialno, int frequency, int offset, int power, int squelch) +bool CDVAPDongle::Initialize(const char *serialno, const int frequency, const int offset, int const power, const int squelch) { bool ok = false; char device[128]; @@ -276,7 +276,7 @@ bool CDVAPDongle::syncit() return false; } -bool CDVAPDongle::get_ser(char *dvp, char *dvap_serial_number) +bool CDVAPDongle::get_ser(const char *dvp, const char *dvap_serial_number) { unsigned cnt = 0; REPLY_TYPE reply; @@ -301,10 +301,10 @@ bool CDVAPDongle::get_ser(char *dvp, char *dvap_serial_number) } while (reply != RT_SER); if (0 == strcmp(dvapreg.param.sstr, dvap_serial_number)) { - printf("Using %s: %s, because serial number matches your dvap_rptr.cfg\n", dvp, dvap_serial_number); + printf("Using %s: %s, because serial number matches your configuration file\n", dvp, dvap_serial_number); return true; } - printf("Device %s has serial %s, but does not match your config value %s\n", dvp, dvapreg.param.sstr, dvap_serial_number); + printf("Device %s has serial %s, but does not match your configuration value %s\n", dvp, dvapreg.param.sstr, dvap_serial_number); return false; } diff --git a/DVAPDongle.h b/DVAPDongle.h index b4d0636..0c2c542 100644 --- a/DVAPDongle.h +++ b/DVAPDongle.h @@ -18,7 +18,7 @@ */ #include - + enum REPLY_TYPE { RT_TIMEOUT, RT_ERR, @@ -66,8 +66,8 @@ typedef struct dvp_register_tag { union { struct { unsigned char flag[3]; - unsigned char rpt1[8]; unsigned char rpt2[8]; + unsigned char rpt1[8]; unsigned char urcall[8]; unsigned char mycall[8]; unsigned char sfx[4]; @@ -88,7 +88,7 @@ class CDVAPDongle public: CDVAPDongle(); ~CDVAPDongle(); - bool Initialize(char *serialno, int frequency, int offset, int power, int squelch); + bool Initialize(const char *serialno, const int frequency, const int offset, const int power, const int squelch); REPLY_TYPE GetReply(SDVAP_REGISTER &dr); void Stop(); int KeepAlive(); @@ -107,7 +107,7 @@ class CDVAPDongle int read_from_dvp(void* buf, unsigned int len); int write_to_dvp(const void* buf, const unsigned int len); bool syncit(); - bool get_ser(char *dvp, char *dvap_serial_number); + bool get_ser(const char *dvp, const char *dvap_serial_number); bool get_name(); bool get_fw(); bool set_modu(); diff --git a/MMDVM.README b/MMDVM.README index 6ca8758..7ce53f2 100644 --- a/MMDVM.README +++ b/MMDVM.README @@ -11,89 +11,73 @@ locale. And make sure you do "sudo apt-get update && sudo apt-get upgrade" befor your start. On a Raspberry Pi, you can do all of this with the configureation menu: "sudo raspi-config". -1) Install the only external library you need: sudo apt-get install libconfig++-dev - Yeah! No wxWidgets! +If you are using a device that uses the GPIO header on the raspberry pi, you need to +disable the serial0 console in the /boot/cmdline.txt file: Remove the reference to +"console=serial0,115200" in this file. You should also disable bluetooth by adding: +"dtoverlay=pi3-disable-bt" (without the quotes) to the end of the /boot/config.txt. - Now in your home directory... +In your home directory... -2) Clone the MMDVMHost repository: git clone git://github.com/g4klx/MMDVMHost.git +1) Clone the MMDVMHost repository: git clone git://github.com/g4klx/MMDVMHost.git -3) cd into the MMDVMHost directory and compile: make +2) cd into the MMDVMHost directory and compile: make If you're system has multiple processors, use: make -jx where x is the number of processors on you system. To tell how many processors you have: cat /proc/cpuinfo | grep processor | wc -l -4) Copy the ini file template: cp MMDVM.ini MMDVM.qn +3) Copy the ini file template: cp MMDVM.ini MMDVM.qn -5) Edit the MMDVM.qn file. Set your Callsign and Id. Turn off duplex. Enable an +4) Edit the MMDVM.qn file. Set your Callsign and Id. Turn off duplex. Enable an external display, if you have one. Set the Frequency(s). Set the Latitude and other location info if you want. Change the log levels. I use DisplayLevel=2 and FileLevel=0. Set the Port on your modem. Disable all services except for D-Star (at least for now). Very important: Set the [D-Star] Module. For UHF use B and for VHF use C. -6) Let's test it! Start it: ./MMDVMHost MMDVM.qn +5) Let's test it! Start it: ./MMDVMHost MMDVM.qn -7) Key your radio and see if the MMDVMHost responds with an acknowledgement. Then +6) Key your radio and see if the MMDVMHost responds with an acknowledgment. Then you'll know that everything on the MMDVM side is fine. -8) Stop the MMDVMHost with a Control-C and edit the MMDVM.qn file again. Change +7) Stop the MMDVMHost with a Control-C and edit the MMDVM.qn file again. Change the [D-Star] AckReply=0 - You won't/don't want this reply from MMDVMhost. You want the acknowledgement + You won't/don't want this reply from MMDVMhost. You want the acknowledgment from QnetGateway, it's more informative. We'll come back to MMDVMHost later, but for now let's work on the QnetGateway. -9) cd back to the home directory and clone the QnetGateway: +8) cd back to the home directory and clone the QnetGateway. Type: git clone git://github.com/n7tae/QnetGateway.git -10) Get into the build directory: cd QnetGateway +9) Get into the build directory. Type: cd QnetGateway + +10) You need a configuration file called qn.cfg for QnetGateway. Start the + configuration script: ./qnconfig + This script will allow you to change any configuration parameter from its + default value. At a minimum, you need to set your login callsign in the ircddb + section and define at least one module. If you want to use the legacy, + closed-source D-Plus reflectors and repeaters, be sure to enable it in the + D-Plus menu. Be sure to write out your configruation file after you have entered + all your changes, then you can exit the configuration script. + +11) Next, your ready to install your software. For that, start the administration + script. Type: ./qnadmin + First you want to create you gwys.txt file. This file contains a list of + reflectors and repeaters you might like to link. Got into the gwys.txt menu + creation sub-menu with "gw" and select one of three different methods to + generate a gwys.txt file. You can add and/or delete records manually in + this sub-menu. Return to the main menu because your now ready to build and + install your system. Install your configured system with "is". This will + compile and install everything that you have configured. + +12) You also need to install your MMDVMHost with "im". -11) Then compile: make - don't forget the -j option! - -12) You need a configuration file called qn.cfg for QnetGateway. A good, nearly - working config file is qn.mmdvm.cfg. Copy it to qn.cfg and edit it. - -13) You need a gwys.txt file for all the systems to which you may wish to link. - If you want to be able to link to repeaters: ./get_gwy_list.sh - If you are only interested in linking to reflectors: ./reflist.sh - This will download and format your gwys.txt file. - If the reflector(s) or repeater(s) you use most often are not present in the - gwys.txt file, you can add them manually, using the same syntax as the existing - entries, at the end of the file. - If you find you can no longer connect to a system, it may be because its IP - address has changed. You can execute either script again, copy it to - /usr/local/etc, and then: either reboot you system, or put " L" in your - URField and key your radio, or: sudo systemctl restart qnlink - -14) We have a gwys.txt file and a qn.cfg in the build directory, so we are ready - to install and start the three QnetGateway services: sudo make install + You should be up and running now! Congratulations! -15) Now it's time to start the MMDVMHost service. You'll do this from the - QnetGateway directory you are aleady in: sudo make installmmdvm - This command will fail if it can't find both MMDVMHost and MMDVM.qn - in the ../MMDVMHost directory. +13) There are many additional things that can be done in the administration menu. + Enter the log menu with "l" and from there you can view logs from each + running process. - You should be up and running now! Congratulations! +14) DTMF is _not_ enabled by default if you want it, type "id" in the main menu. -16) You can see the log of any of the 4 services that make up the QnetGateway + - MMDVMHost system: - sudo journalctl -u qngateway -f - sudo journalctl -u qnlink -f - sudo journalctl -u qnrelay -f - sudo journalctl -u mmdvm -f - - You can do all four of these in one terminal window (that you can detach from!) - by using "screen": sudo apt-get install screen - If you don't know how to use screen: http://aperiodic.net/screen/quick_reference - Being able to detach from a screen session is very useful, especially if you are - operating "headless"! - -17) DTMF is _not_ enabled by default if you want it, you need to do two things: - First, create a working DTMF script. In the build directory: cp qndtmf.sh qndtmf - Then, install the DTMF service: sudo make installdtmf - You should be good to go, The DTMF command "00" should announce the linked - status of you module. See DTMF+REMOTE.README for more information. - -18) Build and start other MMDVMHost services... +15) Build and start other MMDVMHost services... diff --git a/Makefile b/Makefile index 0652766..effcc13 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# Copyright (c) 2018 by Thomas A. Early N7TAE +# Copyright (c) 2018-2019 by Thomas A. Early N7TAE # # 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 @@ -30,37 +30,51 @@ CRONDIR=/etc/cron.d # or, you can choose this for a much smaller executable without debugging help CPPFLAGS=-W -Wall -std=c++11 -Iircddb -DCFG_DIR=\"$(CFGDIR)\" -LDFLAGS=-L/usr/lib -lconfig++ -lrt +LDFLAGS=-L/usr/lib -lrt DSTROBJS = $(IRC)/dstar_dv.o $(IRC)/golay23.o IRCOBJS = $(IRC)/IRCDDB.o $(IRC)/IRCClient.o $(IRC)/IRCReceiver.o $(IRC)/IRCMessageQueue.o $(IRC)/IRCProtocol.o $(IRC)/IRCMessage.o $(IRC)/IRCDDBApp.o $(IRC)/IRCutils.o $(DSTROBJS) SRCS = $(wildcard *.cpp) $(wildcard $(IRC)/*.cpp) OBJS = $(SRCS:.cpp=.o) DEPS = $(SRCS:.cpp=.d) -PROGRAMS=qngateway qnlink qnrelay qndvap qndvrptr qnremote qnvoice -all : $(PROGRAMS) +ALL_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay qndvap qndvrptr qnitap qnmodem +BASE_PROGRAMS=qngateway qnlink qnremote qnvoice -qngateway : $(IRCOBJS) QnetGateway.o aprs.o - g++ $(CPPFLAGS) -o qngateway QnetGateway.o aprs.o $(IRCOBJS) $(LDFLAGS) -pthread +all : $(ALL_PROGRAMS) +base : $(BASE_PROGRAMS) +relay : qnrelay +dvap : qndvap +dvrptr : qndvrptr +itap : qnitap +modem : qnmodem -qnlink : QnetLink.o - g++ $(CPPFLAGS) -o qnlink QnetLink.o $(LDFLAGS) -pthread +qngateway : QnetGateway.o aprs.o UnixDgramSocket.o TCPReaderWriterClient.o QnetConfigure.o $(IRCOBJS) + g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -pthread -qnrelay : QnetRelay.o - g++ $(CPPFLAGS) -o qnrelay QnetRelay.o $(LDFLAGS) +qnlink : QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o UnixDgramSocket.o QnetConfigure.o + g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -pthread -qndvap : QnetDVAP.o DVAPDongle.o $(DSTROBJS) - g++ $(CPPFLAGS) -o qndvap QnetDVAP.o DVAPDongle.o $(DSTROBJS) $(LDFLAGS) -pthread +qnrelay : QnetRelay.o UnixDgramSocket.o QnetConfigure.o + g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -qndvrptr : QnetDVRPTR.o $(DSTROBJS) - g++ $(CPPFLAGS) -o qndvrptr QnetDVRPTR.o $(DSTROBJS) $(LDFLAGS) +qnitap : QnetITAP.o UnixDgramSocket.o QnetConfigure.o + g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -qnremote : QnetRemote.o - g++ $(CPPFLAGS) -o qnremote QnetRemote.o $(LDFLAGS) +qnmodem : QnetModem.o UnixDgramSocket.o QnetConfigure.o + g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -qnvoice : QnetVoice.o - g++ $(CPPFLAGS) -o qnvoice QnetVoice.o $(LDFLAGS) +qndvap : QnetDVAP.o DVAPDongle.o UnixDgramSocket.o QnetConfigure.o $(DSTROBJS) + g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -pthread + +qndvrptr : QnetDVRPTR.o UnixDgramSocket.o QnetConfigure.o $(DSTROBJS) + g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) + +qnremote : QnetRemote.o UnixDgramSocket.o QnetConfigure.o + g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) + +qnvoice : QnetVoice.o QnetConfigure.o + g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) %.o : %.cpp g++ $(CPPFLAGS) -MMD -MD -c $< -o $@ @@ -68,15 +82,21 @@ qnvoice : QnetVoice.o .PHONY: clean clean: - $(RM) $(OBJS) $(DEPS) $(PROGRAMS) + $(RM) $(OBJS) $(DEPS) $(ALL_PROGRAMS) *.gch -include $(DEPS) -install : qngateway qnlink qnrelay +aliases : bash_aliases + /bin/cp -f bash_aliases ~/.bash_aliases + # aliases have been installed in ~/.bash_alises + # You can do 'source bash_aliases' to use them now + +installbase : $(BASE_PROGRAMS) gwys.txt qn.cfg ######### QnetGateway ######### /bin/cp -f qngateway $(BINDIR) /bin/cp -f qnremote qnvoice $(BINDIR) - /bin/cp -f qn.cfg $(CFGDIR) + /bin/ln -s $(shell pwd)/qn.cfg $(CFGDIR) + /bin/cp -f defaults $(CFGDIR) /bin/cp -f system/qngateway.service $(SYSDIR) systemctl enable qngateway.service systemctl daemon-reload @@ -84,85 +104,70 @@ install : qngateway qnlink qnrelay ######### QnetLink ######### /bin/cp -f qnlink $(BINDIR) /bin/cp -f announce/*.dat $(CFGDIR) - /bin/cp -f gwys.txt $(CFGDIR) + /bin/ln -s $(shell pwd)/gwys.txt $(CFGDIR) /bin/cp -f exec_?.sh $(CFGDIR) /bin/cp -f system/qnlink.service $(SYSDIR) systemctl enable qnlink.service systemctl daemon-reload systemctl start qnlink.service + +installrelay : qnrelay ######### QnetRelay ######### - /bin/cp -f qnrelay $(BINDIR) - /bin/cp -f system/qnrelay.service $(SYSDIR) - systemctl enable qnrelay.service + /bin/ln -f qnrelay $(BINDIR)/qnrelay$(MODULE) + sed -e "s/XXX/qnrelay$(MODULE)/" system/qnrelay.service > $(SYSDIR)/qnrelay$(MODULE).service + systemctl enable qnrelay$(MODULE).service systemctl daemon-reload - systemctl start qnrelay.service + systemctl start qnrelay$(MODULE).service -installdvap : qngateway qnlink qndvap - ######### QnetGateway ######### - /bin/cp -f qngateway $(BINDIR) - /bin/cp -f qnremote qnvoice $(BINDIR) - /bin/cp -f qn.cfg $(CFGDIR) - /bin/cp -f system/qngateway.service $(SYSDIR) - systemctl enable qngateway.service +installmmdvm : $(MMPATH)/MMDVMHost $(MMPATH)/MMDVM$(MODULE).qn + ######### MMDVMHost ######### + /bin/ln -f $(MMPATH)/MMDVMHost $(BINDIR)/MMDVMHost$(MODULE) + /bin/ln -s $(shell pwd)/$(MMPATH)/MMDVM$(MODULE).qn $(CFGDIR) + sed -e "s/XXX/MMDVMHost$(MODULE)/" -e "s/YYY/MMDVM$(MODULE)/" system/mmdvm.service > $(SYSDIR)/mmdvm$(MODULE).service + /bin/cp -f system/mmdvm.timer $(SYSDIR)/mmdvm$(MODULE).timer + systemctl enable mmdvm$(MODULE).timer systemctl daemon-reload - systemctl start qngateway.service - ######### QnetLink ######### - /bin/cp -f qnlink $(BINDIR) - /bin/cp -f announce/*.dat $(CFGDIR) - /bin/cp -f gwys.txt $(CFGDIR) - /bin/cp -f exec_?.sh $(CFGDIR) - /bin/cp -f system/qnlink.service $(SYSDIR) - systemctl enable qnlink.service - systemctl daemon-reload - systemctl start qnlink.service - ######### QnetDVAP ######### - /bin/cp -f qndvap $(BINDIR) - /bin/cp -f system/qndvap.service $(SYSDIR) - systemctl enable qndvap.service + systemctl start mmdvm$(MODULE).service + +installitap : qnitap + ######### QnetITAP ######### + /bin/ln -f qnitap $(BINDIR)/qnitap$(MODULE) + sed -e "s/XXX/qnitap$(MODULE)/" system/qnitap.service > $(SYSDIR)/qnitap$(MODULE).service + systemctl enable qnitap$(MODULE).service systemctl daemon-reload - systemctl start qndvap.service + systemctl start qnitap$(MODULE).service -installdvrptr : qngateway qnlink qndvrptr - ######### QnetGateway ######### - /bin/cp -f qngateway $(BINDIR) - /bin/cp -f qnremote qnvoice $(BINDIR) - /bin/cp -f qn.cfg $(CFGDIR) - /bin/cp -f system/qngateway.service $(SYSDIR) - systemctl enable qngateway.service +installmodem : qnmodem + ######### QnetModem ######### + /bin/ln -f qnmodem $(BINDIR)/qnmodem$(MODULE) + sed -e "s/XXX/qnmodem$(MODULE)/" system/qnmodem.service > $(SYSDIR)/qnmodem$(MODULE).service + systemctl enable qnmodem$(MODULE).service systemctl daemon-reload - systemctl start qngateway.service - ######### QnetLink ######### - /bin/cp -f qnlink $(BINDIR) - /bin/cp -f announce/*.dat $(CFGDIR) - /bin/cp -f gwys.txt $(CFGDIR) - /bin/cp -f exec_?.sh $(CFGDIR) - /bin/cp -f system/qnlink.service $(SYSDIR) - systemctl enable qnlink.service + systemctl start qnmodem$(MODULE).service + +installdvap : qndvap + ######### QnetDVAP ######### + /bin/ln -f qndvap $(BINDIR)/qndvap$(MODULE) + sed -e "s/XXX/qndvap$(MODULE)/" system/qndvap.service > $(SYSDIR)/qndvap$(MODULE).service + systemctl enable qndvap$(MODULE).service systemctl daemon-reload - systemctl start qnlink.service + systemctl start qndvap$(MODULE).service + +installdvrptr : qndvrptr ######### QnetDVRPTR ######### - /bin/cp -f qndvrptr $(BINDIR) - /bin/cp -f system/qndvrptr.service $(SYSDIR) - systemctl enable qndvrptr.service + /bin/ln -f qndvrptr $(BINDIR)/qndvrptr$(MODULE) + sed -e "s/XXX/qndvrptr$(MODULE)/" system/qndvrptr.service > $(SYSDIR)/qndvrptr$(MODULE).service + systemctl enable qndvrptr$(MODULE).service systemctl daemon-reload - systemctl start qndvrptr.service + systemctl start qndvrptr$(MODULE).service installdtmf : qndtmf - /bin/cp -f qndtmf $(BINDIR) + /bin/ln -s $(shell pwd)/qndtmf $(BINDIR) /bin/cp -f system/qndtmf.service $(SYSDIR) systemctl enable qndtmf.service systemctl daemon-reload systemctl start qndtmf.service -installmmdvm : - /bin/cp -f $(MMPATH)/MMDVMHost $(BINDIR) - /bin/cp -f $(MMPATH)/MMDVM.qn $(CFGDIR) - /bin/cp -f system/mmdvm.service $(SYSDIR) - /bin/cp -f system/mmdvm.timer $(SYSDIR) - systemctl enable mmdvm.timer - systemctl daemon-reload - systemctl start mmdvm.service - installdash : /usr/bin/apt-get -y install python3-pip /usr/bin/pip3 install libconf @@ -170,16 +175,7 @@ installdash : /bin/cp -f dash/qngdash $(CRONDIR) /bin/sh /usr/bin/python3 $(BINDIR)/qng-info.py & -uninstallmmdvm : - systemctl stop mmdvm.service - systemctl disable mmdvm.timer - /bin/rm -f $(SYSDIR)/mmdvm.service - /bin/rm -f $(SYSDIR)/mmdvm.timer - /bin/rm -f $(BINDIR)/MMDVMHost - /bin/rm -f $(CFGDIR)/MMDVM.qn - sudo systemctl daemon-reload - -uninstall : +uninstallbase : ######### QnetGateway ######### systemctl stop qngateway.service systemctl disable qngateway.service @@ -188,85 +184,65 @@ uninstall : /bin/rm -f $(BINDIR)/qnremote /bin/rm -f $(BINDIR)/qnvoice /bin/rm -f $(CFGDIR)/qn.cfg + /bin/rm -f $(CFGDIR)/defaults ######### QnetLink ######### systemctl stop qnlink.service systemctl disable qnlink.service /bin/rm -f $(SYSDIR)/qnlink.service /bin/rm -f $(BINDIR)/qnlink - /bin/rm -f $(CFGDIR)/already_linked.dat - /bin/rm -f $(CFGDIR)/already_unlinked.dat - /bin/rm -f $(CFGDIR)/failed_linked.dat - /bin/rm -f $(CFGDIR)/id.dat - /bin/rm -f $(CFGDIR)/linked.dat - /bin/rm -f $(CFGDIR)/unlinked.dat + /bin/rm -f $(CFGDIR)/*.dat /bin/rm -f $(CFGDIR)/RPT_STATUS.txt /bin/rm -f $(CFGDIR)/gwys.txt /bin/rm -f $(CFGDIR)/exec_?.sh + +uninstallrelay : ######### QnetRelay ######### - systemctl stop qnrelay.service - systemctl disable qnrelay.service - /bin/rm -f $(SYSDIR)/qnrelay.service - /bin/rm -f $(BINDIR)/qnrelay + systemctl stop qnrelay$(MODULE).service + systemctl disable qnrelay$(MODULE).service + /bin/rm -f $(SYSDIR)/qnrelay$(MODULE).service + /bin/rm -f $(BINDIR)/qnrelay$(MODULE) + systemctl daemon-reload + +uninstallmmdvm : + ######### MMDVMHost ########## + systemctl stop mmdvm$(MODULE).service + systemctl disable mmdvm$(MODULE).timer + /bin/rm -f $(SYSDIR)/mmdvm$(MODULE).service + /bin/rm -f $(SYSDIR)/mmdvm$(MODULE).timer + /bin/rm -f $(BINDIR)/MMDVMHost$(MODULE) + /bin/rm -f $(CFGDIR)/MMDVM$(MODULE).qn + sudo systemctl daemon-reload + +uninstallmodem : + ######### QnetModem ######### + systemctl stop qnmodem$(MODULE).service + systemctl disable qnmodem$(MODULE).service + /bin/rm -f $(SYSDIR)/qnmodem$(MODULE).service + /bin/rm -f $(BINDIR)/qnmodem$(MODULE) + systemctl daemon-reload + +uninstallitap : + ######### QnetITAP ######### + systemctl stop qnitap$(MODULE).service + systemctl disable qnitap$(MODULE).service + /bin/rm -f $(SYSDIR)/qnitap$(MODULE).service + /bin/rm -f $(BINDIR)/qnitap$(MODULE) systemctl daemon-reload uninstalldvap : - ######### QnetGateway ######### - systemctl stop qngateway.service - systemctl disable qngateway.service - /bin/rm -f $(SYSDIR)/qngateway.service - /bin/rm -f $(BINDIR)/qngateway - /bin/rm -f $(BINDIR)/qnremote - /bin/rm -f $(BINDIR)/qnvoice - /bin/rm -f $(CFGDIR)/qn.cfg - ######### QnetLink ######### - systemctl stop qnlink.service - systemctl disable qnlink.service - /bin/rm -f $(SYSDIR)/qnlink.service - /bin/rm -f $(BINDIR)/qnlink - /bin/rm -f $(CFGDIR)/already_linked.dat - /bin/rm -f $(CFGDIR)/already_unlinked.dat - /bin/rm -f $(CFGDIR)/failed_linked.dat - /bin/rm -f $(CFGDIR)/id.dat - /bin/rm -f $(CFGDIR)/linked.dat - /bin/rm -f $(CFGDIR)/unlinked.dat - /bin/rm -f $(CFGDIR)/RPT_STATUS.txt - /bin/rm -f $(CFGDIR)/gwys.txt - /bin/rm -f $(CFGDIR)/exec_?.sh ######### QnetDVAP ######### - systemctl stop qndvap.service - systemctl disable qndvap.service - /bin/rm -f $(SYSDIR)/qndvap.service - /bin/rm -f $(BINDIR)/qndvap + systemctl stop qndvap$(MODULE).service + systemctl disable qndvap$(MODULE).service + /bin/rm -f $(SYSDIR)/qndvap$(MODULE).service + /bin/rm -f $(BINDIR)/qndvap$(MODULE) systemctl daemon-reload uninstalldvrptr : - ######### QnetGateway ######### - systemctl stop qngateway.service - systemctl disable qngateway.service - /bin/rm -f $(SYSDIR)/qngateway.service - /bin/rm -f $(BINDIR)/qngateway - /bin/rm -f $(BINDIR)/qnremote - /bin/rm -f $(BINDIR)/qnvoice - /bin/rm -f $(CFGDIR)/qn.cfg - ######### QnetLink ######### - systemctl stop qnlink.service - systemctl disable qnlink.service - /bin/rm -f $(SYSDIR)/qnlink.service - /bin/rm -f $(BINDIR)/qnlink - /bin/rm -f $(CFGDIR)/already_linked.dat - /bin/rm -f $(CFGDIR)/already_unlinked.dat - /bin/rm -f $(CFGDIR)/failed_linked.dat - /bin/rm -f $(CFGDIR)/id.dat - /bin/rm -f $(CFGDIR)/linked.dat - /bin/rm -f $(CFGDIR)/unlinked.dat - /bin/rm -f $(CFGDIR)/RPT_STATUS.txt - /bin/rm -f $(CFGDIR)/gwys.txt - /bin/rm -f $(CFGDIR)/exec_?.sh ######### QnetDVRPTR ######### - systemctl stop qndvrptr.service - systemctl disable qndvrptr.service - /bin/rm -f $(SYSDIR)/qndvrptr.service - /bin/rm -f $(BINDIR)/qndvrptr + systemctl stop qndvrptr$(MODULE).service + systemctl disable qndvrptr$(MODULE).service + /bin/rm -f $(SYSDIR)/qndvrptr$(MODULE).service + /bin/rm -f $(BINDIR)/qndvrptr$(MODULE) systemctl daemon-reload uninstalldtmf : diff --git a/OPERATING b/OPERATING new file mode 100644 index 0000000..f63558c --- /dev/null +++ b/OPERATING @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2010, 2011, 2012 by Scott Lawson KI4LKF + * addition Copyright (C) 2018 by Thomas A. Early N7TAE + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +I have substantially modified (and hopefully simplified) the configuration +of this software, now, modules QnetGateway, QnetLink, QnetDVAP and QnetDVRPTR +all use a single configuration file. Further, and this is the exciting +part, NEARLY ALL of the parameters these modules need have a useful +default value. If the default value is acceptible to you (and it +probably will be) then you only need to specify what you need to change. + + +This means that for most users, you will only need to specify a few +parameters. In addtion, there is a new script, qnconfig to help you easily +build your configuration file, qn.cfg. + +Some other features are discussed below and are pretty much directly +quoted from KI4LKF original documentation. + +Tom Early, n7tae (at) arrl (dot) net + + +G2_ircDDB adapted from the OpenG2 + + DESCRIPTION +================= + +QnetGateway supports the following commands in YRCALL + +Note: In the commands that folow, _ is a SPACE. + +1) +For Echotest/playback. +YRCALL=_ _ _ _ _ _ _E + +2) +For Voice Mail: +YRCALL=_ _ _ _ _ _ S0 +The above command will Store/create voice mail in a proprietary file +x_voicemail.dat2. +YRCALL=_ _ _ _ _ _ R0 +The above command will Recall/playback voice mail from the recorded file +x_voicemail.dat2. +YRCALL=_ _ _ _ _ _ C0 +The above command will Clear/delete voice mail. File x_voicemail.dat2 will be +deleted. In all cases, the letter x in the file name x_voicemail is the +module A,B or C. + +3) +For inquiring the status of the link: +YRCALL=_ _ _ _ _ _ _I + +4) +For unlinking: +YRCALL=_ _ _ _ _ _ _U + +5) +For linking: +YRCALL=XXNYYYML +Where XXNYYY is a friendly gateway, M is the gateways's module and L is the +LINK command. +YRCALL=XRFNNNML +Where XRFNNN is a friendly reflector, M is the reflector's module and L is +the LINK command. + +By default, if the target is unavailable or becomes unavialable, QnetLink +will attempt to re-link approximately every 50 seconds, at least until +an unlinking command is sent. You will hear status of the connection attempt +about every 50 seconds. If the target sends an unlink request, QnetLink will +honor the request and not attempt to relink until another linking command is +given. This automatic re-linking can be disabled with: + +module_x_auto_link=false + +in your configuration file, qn.cfg. Here the "x" is the module, "a", "b" +or "c". This variable can be set in the qnconfig if executing in +expert mode: + +./qnconfig expert + +Note about linking: +After linking succeeds, set YRCALL=CQCQCQ because your audio will go to the +remote reflector ONLY if YRCALL=CQCQCQ. + +6) +For executing scripts: +YRCALL=_ _ _ _ _ _ nX +where n can be from 0-9 or A-Z. +Example: YRCALL=_ _ _ _ _ _1X +Then the script exec_1.sh will be executed. +Two scripts, exec_R.sh and exec_H.sh are included to reboot and halt your +system, respectively. Also note that rpt1 is passed to these scripts\ +so you can use this as an input parameter for your scripts. +Only admins can execute scripts, so set QnetLink.admin to your callsign + +Required software to run QnetGateway correctly: +--- QnetGateway: The G2 audio gateway. This handle routing using an IRCddb + network and also handle echo and voicemail and some audio + notifications. +--- QnetLink: This communicates with QnetGateway to link the local G2 gateway + to reflectors. +--- QnetDVAP, QnetDVRPTR, QNetITAP (can only be used with a compatible + Icom radio that supports Terminal mode) or QnetRelay (used with + MMDVMHost-compatible modems like the ZUMspot or DVMega). + +ROUTING methods +=============== +Some Dstar routing examples follow. Please do not use the same data because +KJ4NHF is one of our own Dstar repeaters, and KI4LKF is a personal callsign +in our group. + +Example of ZONE routing: + Lets say that your repeater is KJ4NHF, and you are currently on + your local repeater module B, your callsign is KI4LKF + and you want to reach remote gateway XXNYYY module C + In this case, your radio should be programmed like this: + MYCALL=KI4LKF + YRCALL=/XXNYYYC + RPT1=KJ4NHF B + RPT2=KJ4NHF G + +Example of CALLSIGN routing: + Lets say that your repeater is KJ4NHF, and you are currently on + your local repeater module B, your callsign is KI4LKF + and you want to talk to user XX0XXX + In this case, your radio should be programmed like this: + MYCALL=KI4LKF + YRCALL=XX0XXX + RPT1=KJ4NHF B + RPT2=KJ4NHF G + +Example of GROUP routing: + Lets say you want to connect to the DSTAR1 group from your + local repeater module: + MYCALL=KI4LKF + YRCALL=DSTAR1 + RPT1=KJ4NHF B + RPT2=KJ4NHF G + +Example of Cross-Band routing: + Lets say that your repeater is KJ4NHF, and you are currently on + your local repeater module B, your callsign is KI4LKF + and you want to talk from your local module B to your local module C + In this case, your radio should be programmed like this: + MYCALL=KI4LKF + YRCALL=CQCQCQ + RPT1=KJ4NHF B + RPT2=KJ4NHF C + +DTMF decoding and processing +============================= +Prepare the software to decode and process DTMF tones +----------------------------------------------------- +Copy the Shell script qndtmf.sh to qndtmf and make any +changes/additions/subtractions to the script then install your +DTMF script with "sudo make installdtmf" + +Note: When local RF user has entered dtmf tones on the Dstar HT + and then PTT is released, QnetGateway will print the whole + sequence in the QnetGateway.log just before it creates the + dtmf file under /tmp. + +How to enter DTMF tones correctly on your Dstar HT + +What dtmf tones are being decoded and processed +----------------------------------------------- +To link to an XRF reflector: Example: B02102 +That says: link to XRF021 module B. So, we use the # dtmf key +to mean that we're interested in linking to an XRF reflector. The +last two digits are the remote module 01 thru 05 (which is +translated to A thru E). + +To link to a DCS reflector: Example: D00126 +That says: link to DCS001 module Z. So, we use the D dtmf key +to mean that we're interested in linking to a DCS reflector. The +last two digits are the remote module 01 thru 26 (which is +translated to A thru Z). + +To link to REF: Example: *01601 +That says: link to REF016 module A. So, we use the * dtmf key +to mean that we're interested in linking to a REF reflector. The +last two digits are the remote module 01 thru 05 (which is +translated to A thru E). + +To unlink (from any reflector xrf or ref): # + +To get the "link status": 0 or 00 + +Note: +You can extend your dtmf shell script to do more things. like force +your repeater to ID itself. Any YRCALL command that can be executed +by g2link_test, can be added to the shell script. Basically, the +Linux shell script proc_QnetGateway_dtmfs.sh converts the decoded +dtmf tones into YRCALL commands using g2link_test program. + +=========== +QnetLink is a small program that is used to link a local RF repeater +band to a remote reflector. QnetLink software is used by our QnetGateway +(an IRCddb gateway). + +Before we begin, there are some dat files included that are the voice +prompts used by QnetGateway and QnetLink: + +rebooting.dat +baddtmfcmd.dat +gatewayrestart.dat +shutdown.dat +connected2network.dat +notincache.dat +gatewaynotfound.dat +already_linked.dat +already_unlinked.dat +failed_link.dat +linked.dat +unlinked.dat +id.dat + +All of the above dat files are special AMBE format files and they are used +when certain events take place. For example, when the link between your +gateway and a reflector is established, then the file "linked.dat" will +be played over RF, so anyone listening on your repeater, will hear the +announcement that the link has been established. You do not have to +change these files, unless you want to have your own personal voice +played over RF. These dat files were created using a computer and they +are NOT anyone's voice. + +The only file most hams will need to change is id.dat. When the gateway +is not linked and the RF user sets YRCALL=_______I to request the +status of the link, the file id.dat will be played back over RF. But +you can create your own id.dat file that should identify your own +repeater with extra information if you like. + +For example, you could create your own dat audio file that contains +the audio: "This is repeater ...". A simple way to create your own +recorded "repeater identification file" id.dat is to use your Dstar HT +and set YRCALL command: YRCALL=______S0 and key up your repeater. +Start talking, and the gateway will record your audio into the file +x_voicemail.dat2 where x is one of A.B or C. Now copy that file: + +sudo tail -c +56 /tmp/C_voicemail.dat2 > /usr/local/etc/id.dat + +See the DTMF+REMOTE+VOICE.README for more information. + +============ +You may be thinking why did we put all this source code inside QnetLink +instead of putting it all inside the Gateway software QnetGateway. +Having divided the functionality produces a smaller memory and resource +requirement for your computer. + +QnetDVAP + +The serial number required in the .cfg file is obtained from the label +that can be seen on the DVAP circuit board. In most cases set the power +to maximum level. + +QnetDVRPTR + +The serial number required in the qn.cfg file can most easily be obtained +by examining the log file once the board has been powered up. Once you +know the board serial number, edit g2.cfg. After editing g2.cfg file you +can restart the effected program with "sudo systemctl restart qndvrptr". +Or you can always just reboot with "sudo reboot" or "sudo shutdown -r now". + +Rig specific parameters are in "module.x.rf_rx_level", "module.x.inverse.rx" and +"module.x.inverse.tx". You need to play with these to work best with your rig. +With my Kenwood TM-V71, I use the default values. + +You can first start with inverse.rx, trying true or false. Use the echo command +on your radio and look in the logs to see if you are getting into the +dvprtr. Remember to restart the QnetDVRPTR service anytime you modify +/usr/local/etc/g2.cfg. Once you are able to get into the QnetDVRPTR, +then you can work out the inverse.tx paramter. Once you hear anything +with in the echo mode, move rf_rx_level up or down to get the best +audio. + +Once you have a working system, it's a good idea to back up you qn.cfg +files. diff --git a/QnetConfigure.cpp b/QnetConfigure.cpp new file mode 100644 index 0000000..15d7fba --- /dev/null +++ b/QnetConfigure.cpp @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2019 by Thomas A. Early N7TAE + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include "QnetConfigure.h" + +CQnetConfigure::CQnetConfigure() +{ +} + +CQnetConfigure::~CQnetConfigure() +{ + defaults.empty(); + cfg.empty(); +} + +char *CQnetConfigure::Trim(char *s) +{ + size_t len = strlen(s); + while (len && isspace(s[len-1])) + s[--len] = '\0'; + while (*s && len && isspace(*s)) + len = strlen(++s); + return s; +} + +bool CQnetConfigure::ReadConfigFile(const char *configfile, std::map &amap) +{ + FILE *fp = fopen(configfile, "r"); + if (fp) { + char line[2048]; + while (fgets(line, 2048, fp)) { + char *key = strtok(line, "="); + key = Trim(key); + if (strlen(key) && '#' != *key) { + char *val = strtok(NULL, "\r\n"); + char *val2 = Trim(val); + if ('\'' == val2[0]) { + if ('\'' == val2[1]) + val[0] = '\0'; + else + val = strtok(val2, "'"); + } else + val = strtok(val2, "# \t"); + amap[key] = val; + } + } + fclose(fp); + return false; + } + fprintf(stderr, "could not open file %s\n", configfile); + return true; +} + +bool CQnetConfigure::Initialize(const char *file) +{ + std::string filename(CFG_DIR); + filename.append("/defaults"); + if (ReadConfigFile(filename.c_str(), defaults)) + return true; + return ReadConfigFile(file, cfg); +} + +bool CQnetConfigure::KeyExists(const std::string &key) +{ + return (cfg.end() != cfg.find(key)); +} + +bool CQnetConfigure::GetDefaultBool(const std::string &path, const std::string &mod, bool &dvalue) +{ + std::string value; + if (GetDefaultString(path, mod, value)) + return true; // No default value defined! + if ('0'==value.at(0) || 'f'==value.at(0) || 'F'==value.at(0)) + dvalue = false; + else if ('1'==value.at(0) || 't'==value.at(0) || 'T'==value.at(0)) + dvalue = true; + else { + fprintf(stderr, "%s=%s doesn't seem to be a boolean!\n", path.c_str(), value.c_str()); + return true; + } + return false; +} + +bool CQnetConfigure::GetDefaultDouble(const std::string &path, const std::string &mod, double &dvalue) +{ + std::string value; + if (GetDefaultString(path, mod, value)) + return true; // No default value defined! + dvalue = std::stod(value); + return false; +} + +bool CQnetConfigure::GetDefaultInt(const std::string &path, const std::string &mod, int &dvalue) +{ + std::string value; + if (GetDefaultString(path, mod, value)) + return true; // No default value defined! + dvalue = std::stoi(value); + return false; +} + +bool CQnetConfigure::GetDefaultString(const std::string &path, const std::string &mod, std::string &dvalue) +{ + std::string search, search_again; + if (mod.empty()) { + search = path + "_d"; // there is no mod, so this is a simple search + } else { + search_again = mod; // we're looking from a module value. We may have to look for non-generic module parameters + if (0==path.compare(0, 7, "module_") && ('a'==path.at(7) || 'b'==path.at(7) || 'c'==path.at(7)) && '_'==path.at(8)) { + // path begins with module_{a|b|c}_ + if (0==mod.compare("dvrptr") || 0==mod.compare("dvap") || 0==mod.compare("mmdvmhost") || 0==mod.compare("mmdvmmodem") || 0==mod.compare("itap")) { + // and the module is recognized + search = path; + search.replace(7, 1, 1, 'x'); + search_again += path.substr(8); // now the search_again path might look like dvap_frequency, for example. + } else { + fprintf(stderr, "Unrecognized module type = '%s'\n", mod.c_str()); + return true; + } + } else { + fprintf(stderr, "%s looks like an ilformed request from module '%s'\n", path.c_str(), mod.c_str()); + return true; + } + } + auto it = defaults.find(search); + if (defaults.end() == it) { + it = defaults.find(search_again); + if (defaults.end() == it) + return true; + } + dvalue = it->second; + return false; +} + +bool CQnetConfigure::GetValue(const std::string &path, const std::string &mod, bool &value) +{ + auto it = cfg.find(path); + if (cfg.end() == it) { + bool dvalue; + if (GetDefaultBool(path, mod, dvalue)) { + fprintf(stderr, "%s not found in either the cfg file or the defaults file!\n", path.c_str()); + return true; + } + value = dvalue; // found a value in the defaults + } else { // found a value in the cfg file + char c = it->second.at(0); + if ('0'==c || 'f'==c || 'F'==c) + value = false; + else if ('1'==c || 't'==c || 'T'==c) + value = true; + else { + fprintf(stderr, "%s=%s doesn't seem to define a boolean\n", path.c_str(), it->second.c_str()); + return true; + } + } + printf("%s = %s\n", path.c_str(), value ? "true" : "false"); + return false; +} + +bool CQnetConfigure::GetValue(const std::string &path, const std::string &mod, double &value, const double min, const double max) +{ + auto it = cfg.find(path); + if (cfg.end() == it) { + double dvalue; + if (GetDefaultDouble(path, mod, dvalue)) { + fprintf(stderr, "%s not found in either the cfg file or the defaults file!\n", path.c_str()); + return true; + } + if (dvalue < min || dvalue > max) { + fprintf(stderr, "Default value %s=%g is out of acceptable range\n", path.c_str(), value); + return true; + } + value = dvalue; + } else { + value = std::stod(it->second); + if (value < min || value > max) { + fprintf(stderr, "%s=%g is out of acceptable range\n", path.c_str(), value); + return true; + } + } + printf("%s = %g\n", path.c_str(), value); + return false; +} + +bool CQnetConfigure::GetValue(const std::string &path, const std::string &mod, int &value, const int min, const int max) +{ + auto it = cfg.find(path); + if (cfg.end() == it) { + int dvalue; + if (GetDefaultInt(path, mod, dvalue)) { + fprintf(stderr, "%s not found in either the cfg file or the defaults file\n", path.c_str()); + return true; + } + if (dvalue < min || dvalue > max) { + fprintf(stderr, "Default value %s=%d is out of acceptable range\n", path.c_str(), value); + return true; + } + value = dvalue; + } else { + value = std::stoi(it->second); + if (value < min || value > max) { + fprintf(stderr, "%s=%s is out of acceptable range\n", path.c_str(), it->second.c_str()); + return true; + } + } + printf("%s = %d\n", path.c_str(), value); + return false; +} + +bool CQnetConfigure::GetValue(const std::string &path, const std::string &mod, std::string &value, int min, int max) +{ + auto it = cfg.find(path); + if (cfg.end() == it) { + std::string dvalue; + if (GetDefaultString(path, mod, dvalue)) { + fprintf(stderr, "%s not found in either the cfg file or the defaults file\n", path.c_str()); + return true; + } + int l = dvalue.length(); + if (min-1>=l || l>max) { + printf("Default value %s='%s' is wrong size\n", path.c_str(), value.c_str()); + return true; + } + value.assign(dvalue); + } else { + value.assign(it->second); + int l = value.length(); + if (lmax) { + printf("%s='%s' is wrong size\n", path.c_str(), value.c_str()); + return true; + } + } + printf("%s = '%s'\n", path.c_str(), value.c_str()); + return false; +} diff --git a/QnetConfigure.h b/QnetConfigure.h new file mode 100644 index 0000000..efb404f --- /dev/null +++ b/QnetConfigure.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019 by Thomas A. Early N7TAE + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +#include +#include + +class CQnetConfigure { +public: + CQnetConfigure(); + virtual ~CQnetConfigure(); + bool Initialize(const char *configfile); + bool GetValue(const std::string &path, const std::string &mod, bool &value); + bool GetValue(const std::string &path, const std::string &mod, double &value, const double min, const double max); + bool GetValue(const std::string &path, const std::string &mod, int &value, const int min, const int max); + bool GetValue(const std::string &path, const std::string &mod, std::string &value, const int min, const int max); + bool KeyExists(const std::string &key); + +private: + std::map defaults; + std::map cfg; + + char *Trim(char *s); + bool ReadConfigFile(const char *file, std::map &amap); + bool GetDefaultBool (const std::string &key, const std::string &mod, bool &dval); + bool GetDefaultDouble(const std::string &key, const std::string &mod, double &dval); + bool GetDefaultInt (const std::string &key, const std::string &mod, int &dval); + bool GetDefaultString(const std::string &key, const std::string &mod, std::string &dval); +}; diff --git a/QnetDVAP.cpp b/QnetDVAP.cpp index 76648f0..fa892e9 100644 --- a/QnetDVAP.cpp +++ b/QnetDVAP.cpp @@ -37,23 +37,23 @@ #include #include #include -#include "versions.h" #include #include #include #include -#include #include -#include -using namespace libconfig; #include "DVAPDongle.h" #include "QnetTypeDefs.h" +#include "Random.h" +#include "UnixDgramSocket.h" +#include "QnetConfigure.h" +#include "Timer.h" + +#define DVAP_VERSION "QnetDVAP-6.1.1" -#define VERSION DVAP_VERSION #define CALL_SIZE 8 -#define RPTR_SIZE 8 #define IP_SIZE 15 typedef struct dvap_ack_arg_tag { @@ -61,46 +61,47 @@ typedef struct dvap_ack_arg_tag { float ber; } SDVAP_ACK_ARG; +// assigned module, must be A, B or C +static int assigned_module; +// unix sockets +static std::string modem2gate, gate2modem; +static CUnixDgramReader Gate2Modem; +static CUnixDgramWriter Modem2Gate; /* Default configuration data */ -static char RPTR[RPTR_SIZE + 1]; -static char OWNER[RPTR_SIZE + 1]; +static std::string RPTR; +static std::string OWNER; static char RPTR_MOD; -static char RPTR_VIRTUAL_IP[IP_SIZE + 1]; -static int RPTR_PORT; -static char G2_INTERNAL_IP[IP_SIZE + 1]; -static int G2_PORT; -static char DVP_SERIAL[64]; /* APxxxxxx */ -static int DVP_FREQ; /* between 144000000 and 148000000 */ -static int DVP_PWR; /* between -12 and 10 */ -static int DVP_SQL; /* between -128 and -45 */ -static int DVP_OFF; /* between -2000 and 2000 */ -static int WAIT_FOR_PACKETS; /* wait 25 ms in reading from local G2 */ -static int REMOTE_TIMEOUT; /* 1 second */ -static int DELAY_BETWEEN; -static int DELAY_BEFORE; -static bool RPTR_ACK; -static char INVALID_YRCALL_KEY[CALL_SIZE + 1]; +static std::string MODULE_SERIAL_NUMBER; /* APxxxxxx */ +static int MODULE_FREQUENCY; /* between 144000000 and 148000000 */ +static int MODULE_POWER; /* between -12 and 10 */ +static int MODULE_SQUELCH; /* between -128 and -45 */ +static int MODULE_OFFSET; /* between -2000 and 2000 */ +static int MODULE_PACKET_WAIT; /* wait 25 ms in reading from local G2 */ +static int TIMING_TIMEOUT_REMOTE_G2; /* 1 second */ +static int TIMING_PLAY_DELAY; +static int TIMING_PLAY_WAIT; +static bool MODULE_ACKNOWLEDGE; +static double TIMING_TIMEOUT_LOCAL_RPTR; +static bool LOG_DEBUG; +static bool LOG_QSO; static int inactiveMax = 25; /* helper data */ static unsigned char SND_TERM_ID; static char RPTR_and_G[9]; static char RPTR_and_MOD[9]; -static int insock = -1; -static struct sockaddr_in outaddr; static int serfd = -1; static bool busy20000 = false; std::atomic keep_running(true); static unsigned int space = 0; -static unsigned int aseed = 0; /* helper routines */ -static int read_config(const char *cfgFile); +static bool ReadConfig(const char *cfgFile); static void sig_catch(int signum); static int open_sock(); -static void readFrom20000(); +static void ReadFromGateway(); static void calcPFCS(unsigned char *packet, unsigned char *pfcs); static void ReadDVAPThread(); static void RptrAckThread(SDVAP_ACK_ARG *parg); @@ -110,6 +111,7 @@ extern void dstar_dv_init(); extern int dstar_dv_decode(const unsigned char *d, int data[3]); CDVAPDongle dongle; +CRandom Random; static void calcPFCS(unsigned char *packet, unsigned char *pfcs) { @@ -155,284 +157,185 @@ static void sig_catch(int signum) exit(0); } -bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%d]\n", path, value); - return true; -} - -bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%lg]\n", path, value); - return true; -} - -bool get_value(const Config &cfg, const char *path, bool &value, bool default_value) -{ - if (! cfg.lookupValue(path, value)) - value = default_value; - printf("%s = [%s]\n", path, value ? "true" : "false"); - return true; -} - -bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) -{ - if (cfg.lookupValue(path, value)) { - int l = value.length(); - if (lmax) { - printf("%s is invalid\n", path); - return false; - } - } else - value = default_value; - printf("%s = [%s]\n", path, value.c_str()); - return true; -} - /* process configuration file */ -static int read_config(const char *cfgFile) +static bool ReadConfig(const char *cfgFile) { - int i; - Config cfg; + CQnetConfigure cfg; printf("Reading file %s\n", cfgFile); - // Read the file. If there is an error, report it and exit. - try { - cfg.readFile(cfgFile); - } - catch(const FileIOException &fioex) { - printf("Can't read %s\n", cfgFile); - return 1; - } - catch(const ParseException &pex) { - printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); - return 1; - } - - std::string dvap_path, value; - for (i=0; i<3; i++) { - dvap_path = "module."; - dvap_path += ('a' + i); - if (cfg.lookupValue(dvap_path + ".type", value)) { - if (0 == strcasecmp(value.c_str(), "dvap")) + if (cfg.Initialize(cfgFile)) + return true; + + const std::string estr; // an empty string + std::string type; + std::string dvap_path("module_"); + if (0 > assigned_module) { + // we need to find the lone dvap module + for (int i=0; i<3; i++) { + std::string test(dvap_path); + test.append(1, 'a'+i); + if (cfg.KeyExists(test)) { + cfg.GetValue(test, estr, type, 1, 16); + if (type.compare("dvap")) + continue; // this ain't it! + dvap_path.assign(test); + assigned_module = i; break; - } - } - if (i >= 3) { - printf("dvap not defined in any module!\n"); - return 1; - } - RPTR_MOD = 'A' + i; - - if (cfg.lookupValue(std::string(dvap_path+".callsign").c_str(), value) || cfg.lookupValue("ircddb.login", value)) { - int l = value.length(); - if (l<3 || l>CALL_SIZE-2) { - printf("Call '%s' is invalid length!\n", value.c_str()); - return 1; - } else { - for (i=0; i assigned_module) { + fprintf(stderr, "Error: no 'dvap' module found\n!"); + return true; + } } else { - printf("%s.login is not defined!\n", dvap_path.c_str()); - return 1; - } - - if (cfg.lookupValue("ircddb.login", value)) { - int l = value.length(); - if (l<3 || l>CALL_SIZE-2) { - printf("Call '%s' is invalid length!\n", value.c_str()); - return 1; - } else { - for (i=0; i= inactiveMax) { + if (LOG_QSO) + printf("G2 Timeout...\n"); streamid = 0; @@ -577,8 +475,10 @@ static void readFrom20000() busy20000 = false; break; - } else { + } else { // inactive too long if (space == 127) { + if (LOG_DEBUG) + fprintf(stderr, "sending silent frame where: len=%d, inactive=%d\n", len, inactive); if (seq_no == 0) { silence[9] = 0x55; silence[10] = 0x2d; @@ -593,7 +493,7 @@ static void readFrom20000() dr.header = 0xc012u; if (memcmp(silence + 9, sync_codes, 3) == 0) frame_pos_to_dvap = 0; - dr.frame.streamid = stream_id_to_dvap; + dr.frame.streamid = streamid; dr.frame.framepos = frame_pos_to_dvap; dr.frame.seq = seq_to_dvap; memcpy(&dr.frame.vad.voice, silence, 12); @@ -606,120 +506,13 @@ static void readFrom20000() seq_no = 0; } } - } else + } else // busy20000 is false break; } } return; } -int main(int argc, const char **argv) -{ - struct sigaction act; - int rc = -1; - time_t tnow = 0; - time_t ackpoint = 0; - short cnt = 0; - - setvbuf(stdout, NULL, _IOLBF, 0); - printf("dvap_rptr VERSION %s\n", VERSION); - - if (argc != 2) { - printf("Usage: dvap_rptr dvap_rptr.cfg\n"); - return 1; - } - - rc = read_config(argv[1]); - if (rc != 0) { - printf("Failed to process config file %s\n", argv[1]); - return 1; - } - - if (strlen(RPTR) != 8) { - printf("Bad RPTR value, length must be exactly 8 bytes\n"); - return 1; - } - if ((RPTR_MOD != 'A') && (RPTR_MOD != 'B') && (RPTR_MOD != 'C')) { - printf("Bad RPTR_MOD value, must be one of A or B or C\n"); - return 1; - } - - if (RPTR_MOD == 'A') - SND_TERM_ID = 0x03; - else if (RPTR_MOD == 'B') - SND_TERM_ID = 0x01; - else if (RPTR_MOD == 'C') - SND_TERM_ID = 0x02; - - strcpy(RPTR_and_G, RPTR); - RPTR_and_G[7] = 'G'; - - strcpy(RPTR_and_MOD, RPTR); - RPTR_and_MOD[7] = RPTR_MOD; - - time(&tnow); - aseed = tnow + getpid(); - - act.sa_handler = sig_catch; - sigemptyset(&act.sa_mask); - if (sigaction(SIGTERM, &act, 0) != 0) { - printf("sigaction-TERM failed, error=%d\n", errno); - return 1; - } - if (sigaction(SIGHUP, &act, 0) != 0) { - printf("sigaction-HUP failed, error=%d\n", errno); - return 1; - } - if (sigaction(SIGINT, &act, 0) != 0) { - printf("sigaction-INT failed, error=%d\n", errno); - return 1; - } - - /* open dvp */ - if (!dongle.Initialize(DVP_SERIAL, DVP_FREQ, DVP_OFF, DVP_PWR, DVP_SQL)) - return 1; - - rc = open_sock(); - if (rc != 0) { - dongle.Stop(); - close(serfd); - return 1; - } - printf("DVAP opened and initialized!\n"); - dstar_dv_init(); - - std::future readthread; - try { - readthread = std::async(std::launch::async, ReadDVAPThread); - } catch (const std::exception &e) { - printf("Unable to start ReadDVAPThread(). Exception: %s\n", e.what()); - keep_running = false; - } - printf("Started ReadDVAPThread()\n"); - - while (keep_running) { - time(&tnow); - if ((tnow - ackpoint) > 2) { - rc = dongle.KeepAlive(); - if (rc < 0) { - cnt ++; - if (cnt > 5) { - printf("Could not send KEEPALIVE signal to dvap 5 times...exiting\n"); - keep_running = false; - } - } else - cnt = 0; - ackpoint = tnow; - } - readFrom20000(); - } - - readthread.get(); - close(insock); - printf("dvap_rptr exiting\n"); - return 0; -} - static void RptrAckThread(SDVAP_ACK_ARG *parg) { char mycall[8]; @@ -732,9 +525,7 @@ static void RptrAckThread(SDVAP_ACK_ARG *parg) memcpy(RADIO_ID, "BER%", 4); struct sigaction act; - time_t tnow = 0; - unsigned char silence[12] = { 0x4e,0x8d,0x32,0x88,0x26,0x1a,0x3f,0x61,0xe8,0x70,0x4f,0x93 }; - unsigned int aseed_ack = 0; + unsigned char silence[12] = { 0x9e,0x8d,0x32,0x88,0x26,0x1a,0x3f,0x61,0xe8,0x70,0x4f,0x93 }; act.sa_handler = sig_catch; sigemptyset(&act.sa_mask); @@ -751,35 +542,32 @@ static void RptrAckThread(SDVAP_ACK_ARG *parg) return; } - sleep(DELAY_BEFORE); + sleep(TIMING_PLAY_WAIT); - time(&tnow); - aseed_ack = tnow + getpid(); - - uint16_t stream_id_to_dvap = (rand_r(&aseed_ack) % 65535U) + 1U; + uint16_t sid = Random.NewStreamID(); // HEADER while ((space < 1) && keep_running) usleep(5); SDVAP_REGISTER dr; dr.header = 0xa02fu; - dr.frame.streamid = stream_id_to_dvap; + dr.frame.streamid = sid; dr.frame.framepos = 0x80; dr.frame.seq = 0; dr.frame.hdr.flag[0] = 0x01; dr.frame.hdr.flag[1] = dr.frame.hdr.flag[2] = 0x00; - memcpy(dr.frame.hdr.rpt1, RPTR_and_MOD, 8); - memcpy(dr.frame.hdr.rpt2, RPTR_and_G, 8); + memcpy(dr.frame.hdr.rpt2, RPTR_and_MOD, 8); + memcpy(dr.frame.hdr.rpt1, RPTR_and_G, 8); memcpy(dr.frame.hdr.urcall, mycall, 8); memcpy(dr.frame.hdr.mycall, RPTR_and_MOD, 8); - memcpy(dr.frame.hdr.sfx, (unsigned char *)" ", 4); + memcpy(dr.frame.hdr.sfx, (unsigned char *)"DVAP", 4); calcPFCS(dr.frame.hdr.flag, dr.frame.hdr.pfcs); dongle.SendRegister(dr); - std::this_thread::sleep_for(std::chrono::milliseconds(DELAY_BETWEEN)); + std::this_thread::sleep_for(std::chrono::milliseconds(TIMING_PLAY_DELAY)); // SYNC dr.header = 0xc012u; - dr.frame.streamid = stream_id_to_dvap; + dr.frame.streamid = sid; for (int i=0; i<10; i++) { while ((space < 1) && keep_running) usleep(5); @@ -843,7 +631,7 @@ static void RptrAckThread(SDVAP_ACK_ARG *parg) memcpy(&dr.frame.vad.voice, silence, 12); dongle.SendRegister(dr); if (i < 9) - std::this_thread::sleep_for(std::chrono::milliseconds(DELAY_BETWEEN)); + std::this_thread::sleep_for(std::chrono::milliseconds(TIMING_PLAY_DELAY)); } return; } @@ -851,13 +639,9 @@ static void RptrAckThread(SDVAP_ACK_ARG *parg) static void ReadDVAPThread() { REPLY_TYPE reply; - SDSTR net_buf; - SDSTR spack; + SDSVT dsvt; SDVAP_REGISTER dr; - time_t tnow = 0; - time_t S_ctrl_msg_time = 0; - unsigned short C_COUNTER = 0; - time_t last_RF_time = 0; + CTimer last_RF_time; struct sigaction act; bool dvap_busy = false; // bool ptt = false; @@ -868,8 +652,6 @@ static void ReadDVAPThread() short int sequence = 0; char mycall[8]; short int status_cntr = 3000; - char temp_yrcall[CALL_SIZE + 1]; - char *temp_ptr = NULL; int num_dv_frames = 0; int num_bit_errors = 0; @@ -892,32 +674,11 @@ static void ReadDVAPThread() return; } - /* prepare the S server status packet */ - memcpy(spack.pkt_id, "DSTR", 4); - spack.counter = 0; - spack.flag[0] = 0x73; - spack.flag[1] = 0x21; - spack.flag[2] = 0x00; - spack.remaining = 0x10; - while (keep_running) { - time(&tnow); - - /* send the S packet if needed */ - if ((tnow - S_ctrl_msg_time) > 60) { - spack.counter = C_COUNTER++; - memcpy(spack.spkt.mycall, OWNER, 7); - spack.spkt.mycall[7] = 'S'; - memcpy(spack.spkt.rpt, OWNER, 7); - spack.spkt.rpt[7] = 'S'; - sendto(insock, spack.pkt_id, 26, 0, (struct sockaddr *)&outaddr, sizeof(outaddr)); - S_ctrl_msg_time = tnow; - } // local RF user went away ? if (dvap_busy) { - time(&tnow); - if ((tnow - last_RF_time) > 1) + if (last_RF_time.time() > TIMING_TIMEOUT_LOCAL_RPTR) dvap_busy = false; } @@ -942,10 +703,8 @@ static void ReadDVAPThread() num_dv_frames = 0; num_bit_errors = 0; - printf("From DVAP: flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s\n", - dr.frame.hdr.flag[0], dr.frame.hdr.flag[1], dr.frame.hdr.flag[2], - dr.frame.hdr.mycall, dr.frame.hdr.sfx, dr.frame.hdr.urcall, - dr.frame.hdr.rpt2, dr.frame.hdr.rpt1); + if (LOG_QSO) + printf("From DVAP: flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s\n", dr.frame.hdr.flag[0], dr.frame.hdr.flag[1], dr.frame.hdr.flag[2], dr.frame.hdr.mycall, dr.frame.hdr.sfx, dr.frame.hdr.urcall, dr.frame.hdr.rpt1, dr.frame.hdr.rpt2); ok = true; @@ -959,40 +718,28 @@ static void ReadDVAPThread() ok = false; } - /* Reject those stupid STN stations */ - if (ok) { - memcpy(temp_yrcall, dr.frame.hdr.urcall, CALL_SIZE); - temp_yrcall[CALL_SIZE] = '\0'; - temp_ptr = strstr(temp_yrcall, INVALID_YRCALL_KEY); - if (temp_ptr == temp_yrcall) { // found it at first position - printf("YRCALL value [%s] starts with the INVALID_YRCALL_KEY [%s], resetting to CQCQCQ\n", - temp_yrcall, INVALID_YRCALL_KEY); - memcpy(dr.frame.hdr.urcall, "CQCQCQ ", 8); - } - } - - memcpy(&net_buf.vpkt.hdr, dr.frame.hdr.flag, 41); // copy the header + memcpy(dsvt.hdr.flag, dr.frame.hdr.flag, 41); // copy the header /* RPT1 must always be the repeater + module */ - memcpy(net_buf.vpkt.hdr.r1, RPTR_and_MOD, 8); + memcpy(dsvt.hdr.rpt1, RPTR_and_MOD, 8); /* copy RPT2 */ - memcpy(net_buf.vpkt.hdr.r2, dr.frame.hdr.rpt1, 8); + memcpy(dsvt.hdr.rpt2, dr.frame.hdr.rpt2, 8); /* RPT2 must also be valid */ - if ((net_buf.vpkt.hdr.r2[7] == 'A') || - (net_buf.vpkt.hdr.r2[7] == 'B') || - (net_buf.vpkt.hdr.r2[7] == 'C') || - (net_buf.vpkt.hdr.r2[7] == 'G')) - memcpy(net_buf.vpkt.hdr.r2, RPTR, 7); + if ((dsvt.hdr.rpt2[7] == 'A') || + (dsvt.hdr.rpt2[7] == 'B') || + (dsvt.hdr.rpt2[7] == 'C') || + (dsvt.hdr.rpt2[7] == 'G')) + memcpy(dsvt.hdr.rpt2, RPTR.c_str(), 7); else - memset(net_buf.vpkt.hdr.r2, ' ', 8); + memset(dsvt.hdr.rpt2, ' ', 8); - if ((memcmp(net_buf.vpkt.hdr.ur, "CQCQCQ", 6) != 0) && (net_buf.vpkt.hdr.r2[0] != ' ')) - memcpy(net_buf.vpkt.hdr.r2, RPTR_and_G, 8); + if ((memcmp(dsvt.hdr.urcall, "CQCQCQ", 6) != 0) && (dsvt.hdr.rpt2[0] != ' ')) + memcpy(dsvt.hdr.rpt2, RPTR_and_G, 8); /* 8th in rpt1, rpt2 must be diff */ - if (net_buf.vpkt.hdr.r2[7] == net_buf.vpkt.hdr.r1[7]) - memset(net_buf.vpkt.hdr.r2, ' ', 8); + if (dsvt.hdr.rpt2[7] == dsvt.hdr.rpt1[7]) + memset(dsvt.hdr.rpt2, ' ', 8); /* Are we restricting the RF user ? @@ -1001,22 +748,22 @@ static void ReadDVAPThread() that means that mycall, rpt1, rpt2 must be equal to RPTR otherwise we drop the rf data */ - if (memcmp(RPTR, OWNER, RPTR_SIZE) != 0) { - if (memcmp(net_buf.vpkt.hdr.my, RPTR, RPTR_SIZE) != 0) { - printf("mycall=[%.8s], not equal to %s\n", net_buf.vpkt.hdr.my, RPTR); + if (RPTR.compare(OWNER)) { + if (memcmp(dsvt.hdr.mycall, RPTR.c_str(), CALL_SIZE) != 0) { + printf("mycall=[%.8s], not equal to %s\n", dsvt.hdr.mycall, RPTR.c_str()); ok = false; } - } else if (memcmp(net_buf.vpkt.hdr.my, " ", 8) == 0) { - printf("Invalid value for mycall=[%.8s]\n", net_buf.vpkt.hdr.my); + } else if (memcmp(dsvt.hdr.mycall, " ", 8) == 0) { + printf("Invalid value for mycall=[%.8s]\n", dsvt.hdr.mycall); ok = false; } if (ok) { for (i = 0; i < 8; i++) { - if (!isupper(net_buf.vpkt.hdr.my[i]) && - !isdigit(net_buf.vpkt.hdr.my[i]) && - (net_buf.vpkt.hdr.my[i] != ' ')) { - memset(net_buf.vpkt.hdr.my, ' ', 8); + if (!isupper(dsvt.hdr.mycall[i]) && + !isdigit(dsvt.hdr.mycall[i]) && + (dsvt.hdr.mycall[i] != ' ')) { + memset(dsvt.hdr.mycall, ' ', 8); ok = false; printf("Invalid value for MYCALL\n"); break; @@ -1024,74 +771,60 @@ static void ReadDVAPThread() } for (i = 0; i < 4; i++) { - if (!isupper(net_buf.vpkt.hdr.nm[i]) && - !isdigit(net_buf.vpkt.hdr.nm[i]) && - (net_buf.vpkt.hdr.nm[i] != ' ')) { - memset(net_buf.vpkt.hdr.nm, ' ', 4); + if (!isupper(dsvt.hdr.sfx[i]) && + !isdigit(dsvt.hdr.sfx[i]) && + (dsvt.hdr.sfx[i] != ' ')) { + memset(dsvt.hdr.sfx, ' ', 4); break; } } for (i = 0; i < 8; i++) { - if (!isupper(net_buf.vpkt.hdr.ur[i]) && - !isdigit(net_buf.vpkt.hdr.ur[i]) && - (net_buf.vpkt.hdr.ur[i] != ' ') && - (net_buf.vpkt.hdr.ur[i] != '/')) { - memcpy(net_buf.vpkt.hdr.ur, "CQCQCQ ", 8); + if (!isupper(dsvt.hdr.urcall[i]) && + !isdigit(dsvt.hdr.urcall[i]) && + (dsvt.hdr.urcall[i] != ' ') && + (dsvt.hdr.urcall[i] != '/')) { + memcpy(dsvt.hdr.urcall, "CQCQCQ ", 8); break; } } /*** what if YRCALL is all spaces, we can NOT allow that ***/ - if (memcmp(net_buf.vpkt.hdr.ur, " ", 8) == 0) - memcpy(net_buf.vpkt.hdr.ur, "CQCQCQ ", 8); + if (memcmp(dsvt.hdr.urcall, " ", 8) == 0) + memcpy(dsvt.hdr.urcall, "CQCQCQ ", 8); /* change the rptr flags to net flags */ if (dr.frame.hdr.flag[0] == 0x40) - net_buf.vpkt.hdr.flag[0] = 0x00; + dsvt.hdr.flag[0] = 0x00; else if (dr.frame.hdr.flag[0] == 0x48) - net_buf.vpkt.hdr.flag[0] = 0x08; + dsvt.hdr.flag[0] = 0x08; else if (dr.frame.hdr.flag[0] == 0x60) - net_buf.vpkt.hdr.flag[0] = 0x20; + dsvt.hdr.flag[0] = 0x20; else if (dr.frame.hdr.flag[0] == 0x68) - net_buf.vpkt.hdr.flag[0] = 0x28; + dsvt.hdr.flag[0] = 0x28; else - net_buf.vpkt.hdr.flag[0] = 0x00; - net_buf.vpkt.hdr.flag[1] = net_buf.vpkt.hdr.flag[2] = 0x00; - - /* for icom g2 */ - spack.counter = C_COUNTER++; - memcpy(spack.spkt.mycall, net_buf.vpkt.hdr.my, 8); - memcpy(spack.spkt.rpt, OWNER, 7); - spack.spkt.rpt[7] = RPTR_MOD; - sendto(insock, spack.pkt_id, 26, 0, (struct sockaddr *)&outaddr, sizeof(outaddr)); + dsvt.hdr.flag[0] = 0x00; + dsvt.hdr.flag[1] = dsvt.hdr.flag[2] = 0x00; // Before we send the data to the local gateway, // set RPT1, RPT2 to be the local gateway - memcpy(net_buf.vpkt.hdr.r1, OWNER, 7); - if (net_buf.vpkt.hdr.r2[7] != ' ') - memcpy(net_buf.vpkt.hdr.r2, OWNER, 7); - - memcpy(net_buf.pkt_id, "DSTR", 4); - net_buf.counter = C_COUNTER++; - net_buf.flag[0] = 0x73; - net_buf.flag[1] = 0x12; - net_buf.flag[2] = 0x00; - net_buf.remaining = 0x30; - net_buf.vpkt.icm_id = 0x20; - net_buf.vpkt.dst_rptr_id = 0x00; - net_buf.vpkt.snd_rptr_id = 0x01; - net_buf.vpkt.snd_term_id = SND_TERM_ID; - streamid_raw = (rand_r(&aseed) % 65535U) + 1U; - net_buf.vpkt.streamid = streamid_raw; - net_buf.vpkt.ctrl = 0x80; + memcpy(dsvt.hdr.rpt1, OWNER.c_str(), 7); + if (dsvt.hdr.rpt2[7] != ' ') + memcpy(dsvt.hdr.rpt2, OWNER.c_str(), 7); + + memcpy(dsvt.title, "DSVT", 4); + dsvt.config = 0x10U; + dsvt.id = 0x20; + streamid_raw = Random.NewStreamID(); + dsvt.streamid = streamid_raw; + dsvt.ctrl = 0x80; sequence = 0; - calcPFCS((unsigned char *)&(net_buf.vpkt.hdr), net_buf.vpkt.hdr.pfcs); - sendto(insock, &net_buf, 58, 0, (struct sockaddr *)&outaddr, sizeof(outaddr)); + calcPFCS((unsigned char *)&(dsvt.hdr), dsvt.hdr.pfcs); + Modem2Gate.Write(dsvt.title, 56); // local RF user keying up, start timer dvap_busy = true; - time(&last_RF_time); + last_RF_time.start(); // save mycall for the ack later memcpy(mycall, dr.frame.hdr.mycall, 8); @@ -1102,16 +835,15 @@ static void ReadDVAPThread() if (dvap_busy) { the_end = ((dr.frame.framepos & 0x40) == 0x40); - net_buf.counter = C_COUNTER++; - net_buf.remaining = 0x13; - net_buf.vpkt.ctrl = sequence++; + dsvt.config = 0x20U; + dsvt.ctrl = sequence++; if (the_end) - net_buf.vpkt.ctrl = sequence | 0x40; - memcpy(&net_buf.vpkt.vasd, &dr.frame.vad.voice, 12); - sendto(insock, &net_buf, 29, 0, (struct sockaddr *)&outaddr, sizeof(outaddr)); + dsvt.ctrl = sequence | 0x40; + memcpy(&dsvt.vasd, &dr.frame.vad.voice, 12); + Modem2Gate.Write(dsvt.title, 27); int ber_data[3]; - int ber_errs = dstar_dv_decode(net_buf.vpkt.vasd.voice, ber_data); + int ber_errs = dstar_dv_decode(dsvt.vasd.voice, ber_data); if (ber_data[0] != 0xf85) { num_bit_errors += ber_errs; num_dv_frames++; @@ -1121,15 +853,16 @@ static void ReadDVAPThread() sequence = 0; // local RF user still talking, update timer - time(&last_RF_time); + last_RF_time.start(); if (the_end) { // local RF user stopped talking dvap_busy = false; static SDVAP_ACK_ARG dvap_ack_arg; dvap_ack_arg.ber = (num_dv_frames==0) ? 0.f : 100.f * (float)num_bit_errors / (float)(num_dv_frames * 24); - printf("End of dvap audio, ber=%.02f\n", dvap_ack_arg.ber); + if (LOG_QSO) + printf("End of dvap audio, ber=%.02f\n", dvap_ack_arg.ber); - if (RPTR_ACK && !busy20000) { + if (MODULE_ACKNOWLEDGE && !busy20000) { memcpy(dvap_ack_arg.mycall, mycall, 8); try { std::async(std::launch::async, RptrAckThread, &dvap_ack_arg); @@ -1155,3 +888,137 @@ static void ReadDVAPThread() keep_running = false; return; } + +int main(int argc, const char **argv) +{ + struct sigaction act; + int rc = -1; + short cnt = 0; + + setvbuf(stdout, NULL, _IOLBF, 0); + printf("dvap_rptr VERSION %s\n", DVAP_VERSION); + + if (argc != 2) { + fprintf(stderr, "Usage: %s dvap_rptr.cfg\n", argv[0]); + return 1; + } + + if ('-' == argv[1][0]) { + printf("\nQnetDVAP Version #%s Copyright (C) 2018-2019 by Thomas A. Early N7TAE\n", DVAP_VERSION); + printf("QnetDVAP comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); + printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n"); + return 0; + } + + const char *qn = strstr(argv[0], "qndvap"); + if (NULL == qn) { + fprintf(stderr, "Error finding 'qndvap' in %s!\n", argv[0]); + return 1; + } + qn += 6; + + switch (*qn) { + case NULL: + assigned_module = -1; + break; + case 'a': + assigned_module = 0; + break; + case 'b': + assigned_module = 1; + break; + case 'c': + assigned_module = 2; + break; + default: + fprintf(stderr, "ERROR: '%s' is not a valid module\nassigned module must be a, b or c\n", argv[1]); + return 1; + } + + if (ReadConfig(argv[1])) { + printf("Failed to process config file %s\n", argv[2]); + return 1; + } + + if (RPTR.length() != 8) { + printf("Bad RPTR value, length must be exactly 8 bytes\n"); + return 1; + } + if ((RPTR_MOD != 'A') && (RPTR_MOD != 'B') && (RPTR_MOD != 'C')) { + printf("Bad RPTR_MOD value, must be one of A or B or C\n"); + return 1; + } + + if (RPTR_MOD == 'A') + SND_TERM_ID = 0x03; + else if (RPTR_MOD == 'B') + SND_TERM_ID = 0x01; + else if (RPTR_MOD == 'C') + SND_TERM_ID = 0x02; + + strcpy(RPTR_and_G, RPTR.c_str()); + RPTR_and_G[7] = 'G'; + + strcpy(RPTR_and_MOD, RPTR.c_str()); + RPTR_and_MOD[7] = RPTR_MOD; + + CTimer ackpoint; + + act.sa_handler = sig_catch; + sigemptyset(&act.sa_mask); + if (sigaction(SIGTERM, &act, 0) != 0) { + printf("sigaction-TERM failed, error=%d\n", errno); + return 1; + } + if (sigaction(SIGHUP, &act, 0) != 0) { + printf("sigaction-HUP failed, error=%d\n", errno); + return 1; + } + if (sigaction(SIGINT, &act, 0) != 0) { + printf("sigaction-INT failed, error=%d\n", errno); + return 1; + } + + /* open dvp */ + if (!dongle.Initialize(MODULE_SERIAL_NUMBER.c_str(), MODULE_FREQUENCY, MODULE_OFFSET, MODULE_POWER, MODULE_SQUELCH)) + return 1; + + rc = open_sock(); + if (rc != 0) { + dongle.Stop(); + close(serfd); + return 1; + } + printf("DVAP opened and initialized!\n"); + dstar_dv_init(); + + std::future readthread; + try { + readthread = std::async(std::launch::async, ReadDVAPThread); + } catch (const std::exception &e) { + printf("Unable to start ReadDVAPThread(). Exception: %s\n", e.what()); + keep_running = false; + } + printf("Started ReadDVAPThread()\n"); + + while (keep_running) { + if (ackpoint.time() > 2.5) { + rc = dongle.KeepAlive(); + if (rc < 0) { + cnt ++; + if (cnt > 5) { + printf("Could not send KEEPALIVE signal to dvap 5 times...exiting\n"); + keep_running = false; + } + } else + cnt = 0; + ackpoint.start(); + } + ReadFromGateway(); + } + + readthread.get(); + Gate2Modem.Close(); + printf("QnetDVAP exiting\n"); + return 0; +} diff --git a/QnetDVRPTR.cpp b/QnetDVRPTR.cpp index 670a646..7ee2d09 100644 --- a/QnetDVRPTR.cpp +++ b/QnetDVRPTR.cpp @@ -21,16 +21,17 @@ #include #include #include -#include "versions.h" #include -#include -using namespace libconfig; +#include "Random.h" +#include "UnixDgramSocket.h" +#include "QnetConfigure.h" +#include "QnetTypeDefs.h" + +#define DVRPTR_VERSION "QnetDVRPTR-6.0.5" -#define VERSION DVRPTR_VERSION #define BAUD B115200 #define CALL_SIZE 8 -#define RPTR_SIZE 8 #define IP_SIZE 15 /*** BER stuff ***/ @@ -50,24 +51,20 @@ short seq_no3 = 0; int fd_ser = -1; bool busy20000 = false; -static unsigned char S_packet[26]; -static time_t S_ctrl_msg_time = 0; static int rqst_count = 6; -static unsigned streamid[2] = {0x00, 0x00}; +static unsigned short streamid = 0x0; static unsigned char start_Header[8]= {0xD0,0x03,0x00,0x16,0x01,0x00,0x00,0x00}; static unsigned char ptt_off[8]= {0xD0,0x03,0x00,0x1A,0x01,0xff,0x00,0x00}; -static int insock = -1; -static int read_config(const char *cfgFile); +static bool ReadConfig(const char *cfgFile); static void readFrom20000(); static bool check_serial(); -static unsigned char Send_Network_Header[58]; -static unsigned char Send_Network_Audio [29]; +SDSVT Send_Network_Header; +SDSVT Send_Network_Audio; static int inactiveMax = 3200; -static unsigned short C_COUNTER = 0; static unsigned char Send_Modem_Header[52]; static unsigned char writevoice[24]; @@ -80,27 +77,27 @@ static unsigned char Modem_Init2[12]= {0xD0,0x07,0x00,0x14,0xC0,0x04,0x00,0x57,0 static unsigned char Modem_STATUS[6]= {0xD0,0x01,0x00,0x10,0x00,0x00}; // Status Abfragr static unsigned char Modem_SERIAL[6]= {0xD0,0x01,0x00,0x12,0x00,0x00}; -static char DVRPTR_SERIAL[16]; -static char DVCALL[RPTR_SIZE + 1] = {"ABCDEF"}; -static char RPTR[RPTR_SIZE + 1] = {"ABCDEF"}; -static char GATEWAY_IP[IP_SIZE + 1] = {"127.0.0.1"}; -static int GATEWAY_PORT = 20000; -static char DVRPTR_INTERNAL_IP[IP_SIZE + 1] = {"127.0.0.1"}; -static int DVRPTR_INTERNAL_PORT = 20000; +static int assigned_module; +static std::string gate2modem, modem2gate; +CUnixDgramWriter Modem2Gate; +CUnixDgramReader Gate2Modem; + +static std::string DVRPTR_SERIAL; +static char DVCALL[CALL_SIZE + 1]; +static char RPTR[CALL_SIZE + 1]; static char DVRPTR_MOD = 'B'; static int RF_AUDIO_Level = 10; static bool DUPLEX = true; -static long ACK_DELAY = 200000; +static int ACK_DELAY = 200000; static int DELAY_BETWEEN = 20000; static bool RPTR_ACK = true; -static char ENABLE_RF[RPTR_SIZE + 1] = {" "}; -static char DISABLE_RF[RPTR_SIZE + 1] = {" "}; +static char ENABLE_RF[CALL_SIZE + 1]; +static char DISABLE_RF[CALL_SIZE + 1]; static bool IS_ENABLED = true; static bool ok = false; -static bool RX_Inverse = 0; -static bool TX_Inverse = 0; -static int TX_DELAY = 250; /* in milliseconds */ -static struct sockaddr_in outaddr; +static bool RX_Inverse = true; +static bool TX_Inverse = true; +static int TX_DELAY; /* in milliseconds */ static unsigned char SND_TERM_ID = 0x00; static char DVCALL_and_G[9]; static char DVCALL_and_MOD[9]; @@ -112,66 +109,19 @@ static char myRPT1[10]; //RX from HF RPT1 static char myUR[10]; static char myCall[10]; static char myCall2[10]; -static char INVALID_YRCALL_KEY[CALL_SIZE + 1] = { "" }; - -char Ergebnis[250]; - -struct hdr { - unsigned char flags[3]; - unsigned char rpt2[8]; - unsigned char rpt1[8]; - unsigned char urcall[8]; - unsigned char mycall[8]; - unsigned char sfx[4]; - unsigned char pfcs[2]; -}; -struct icm { - unsigned char icm_id; - unsigned char dst_rptr_id; - unsigned char snd_rptr_id; - unsigned char snd_term_id; - unsigned char streamid[2]; - unsigned char ctrl; -}; -struct voice_and_text { - unsigned char voice[8]; - unsigned char text[2]; -}; - -struct nettext { - unsigned char net_text[20]; -}; - -struct audio { - unsigned char buff[sizeof(struct hdr)]; -}; - -struct pkt { - unsigned char pkt_id[4]; - unsigned char nothing1[2]; - unsigned char flags[2]; - unsigned char nothing2[2]; - struct icm myicm; - union { - struct audio rf_audio; - struct hdr rf_hdr; - struct nettext net_text; - struct voice_and_text vat; - }; -}; -static struct pkt recv_buf; -typedef unsigned int U32; +char cbuf[250]; +static SDSVT recv_buf; //! 32-bit union. typedef union { - U32 u32 ; - unsigned short u16[2]; - unsigned char u8[4]; + uint32_t u32; + uint16_t u16[2]; + uint8_t u8[4]; } Union32; -static const U32 ENCODING_TABLE_23127[] = { +static const uint32_t ENCODING_TABLE_23127[] = { 0x000000U, 0x0018EAU, 0x00293EU, 0x0031D4U, 0x004A96U, 0x00527CU, 0x0063A8U, 0x007B42U, 0x008DC6U, 0x00952CU, 0x00A4F8U, 0x00BC12U, 0x00C750U, 0x00DFBAU, 0x00EE6EU, 0x00F684U, 0x010366U, 0x011B8CU, 0x012A58U, 0x0132B2U, 0x0149F0U, 0x01511AU, 0x0160CEU, 0x017824U, 0x018EA0U, 0x01964AU, 0x01A79EU, 0x01BF74U, 0x01C436U, 0x01DCDCU, @@ -584,7 +534,7 @@ static const U32 ENCODING_TABLE_23127[] = { 0xFFAD82U, 0xFFB568U, 0xFFCE2AU, 0xFFD6C0U, 0xFFE714U, 0xFFFFFEU }; -static const U32 ENCODING_TABLE_24128[] = { +static const uint32_t ENCODING_TABLE_24128[] = { 0x000000U, 0x0018EBU, 0x00293EU, 0x0031D5U, 0x004A97U, 0x00527CU, 0x0063A9U, 0x007B42U, 0x008DC6U, 0x00952DU, 0x00A4F8U, 0x00BC13U, 0x00C751U, 0x00DFBAU, 0x00EE6FU, 0x00F684U, 0x010367U, 0x011B8CU, 0x012A59U, 0x0132B2U, 0x0149F0U, 0x01511BU, 0x0160CEU, 0x017825U, 0x018EA1U, 0x01964AU, 0x01A79FU, 0x01BF74U, 0x01C436U, 0x01DCDDU, @@ -998,7 +948,7 @@ static const U32 ENCODING_TABLE_24128[] = { }; -static const U32 DECODING_TABLE_23127[] = { +static const uint32_t DECODING_TABLE_23127[] = { 0x000000U, 0x000001U, 0x000002U, 0x000003U, 0x000004U, 0x000005U, 0x000006U, 0x000007U, 0x000008U, 0x000009U, 0x00000AU, 0x00000BU, 0x00000CU, 0x00000DU, 0x00000EU, 0x024020U, 0x000010U, 0x000011U, 0x000012U, 0x000013U, 0x000014U, 0x000015U, 0x000016U, 0x412000U, 0x000018U, 0x000019U, 0x00001AU, 0x180800U, 0x00001CU, 0x200300U, @@ -1221,9 +1171,9 @@ static const U32 DECODING_TABLE_23127[] = { * obtain its syndrome in decoding. */ -static U32 get_syndrome_23127(U32 pattern) +static uint32_t get_syndrome_23127(uint32_t pattern) { - U32 aux = GORLAY_X22; + uint32_t aux = GORLAY_X22; if (pattern >= GORLAY_X11) { while (pattern & GORLAY_MASK12) { while ((aux & pattern)==0) aux >>= 1; @@ -1241,8 +1191,8 @@ __inline unsigned int gorlay_encode23127(unsigned int data) unsigned int gorlay_decode23127(unsigned int code) { - U32 syndrome = get_syndrome_23127(code); - U32 error_pattern = DECODING_TABLE_23127[syndrome]; + uint32_t syndrome = get_syndrome_23127(code); + uint32_t error_pattern = DECODING_TABLE_23127[syndrome]; code ^= error_pattern; return code >> 11; } @@ -1257,7 +1207,7 @@ __inline unsigned int gorlay_decode24128(unsigned int code) return gorlay_decode23127(code >> 1); } -static const U32 PRNG_TABLE[] = { +static const uint32_t PRNG_TABLE[] = { 0x42CC47U, 0x19D6FEU, 0x304729U, 0x6B2CD0U, 0x60BF47U, 0x39650EU, 0x7354F1U, 0xEACF60U, 0x819C9FU, 0xDE25CEU, 0xD7B745U, 0x8CC8B8U, 0x8D592BU, 0xF71257U, 0xBCA084U, 0xA5B329U, 0xEE6AFAU, 0xF7D9A7U, 0xBCC21CU, 0x4712D9U, 0x4F2922U, 0x14FA37U, 0x5D43ECU, 0x564115U, 0x299A92U, 0x20A9EBU, 0x7B707DU, 0x3BE3A4U, 0x20D95BU, 0x6B085AU, @@ -1678,7 +1628,7 @@ typedef unsigned char tambevoice[6]; static void ambefec_deinterleave(tambevoicefec result, const tambevoicefec voice) { - U32 bitpos, bytcnt; + uint32_t bitpos, bytcnt; memset(result, 0, sizeof(tambevoicefec)); // init result bitpos = 0; for (bytcnt = 0; bytcnt < sizeof(tambevoicefec); bytcnt++) { @@ -1704,7 +1654,7 @@ static void ambefec_deinterleave(tambevoicefec result, const tambevoicefec voice static void ambefec_interleave(tambevoicefec result, const tambevoicefec raw_voice) { - U32 bitpos, bytcnt; + uint32_t bitpos, bytcnt; bitpos = 0; for (bytcnt = 0; bytcnt < sizeof(tambevoicefec); bytcnt++) { char voice_dsr = (raw_voice[bitpos>>3] & (0x80 >> (bitpos&7)))?0x80:0x00; @@ -1730,7 +1680,7 @@ static void ambefec_interleave(tambevoicefec result, const tambevoicefec raw_voi void ambefec_regenerate(tambevoicefec voice) { tambevoicefec decoded; - U32 data, datb, encoded_dat; + uint32_t data, datb, encoded_dat; ambefec_deinterleave(decoded, voice); data = gorlay_decode24128((decoded[0]<<16) | (decoded[1]<<8) | decoded[2]); encoded_dat = gorlay_encode24128(data); @@ -1746,7 +1696,7 @@ void ambefec_regenerate(tambevoicefec voice) ambefec_interleave(voice, decoded); } -static unsigned char silence[12] = { 0x4e,0x8d,0x32,0x88,0x26,0x1a,0x3f,0x61,0xe8,0x70,0x4f,0x93 }; +static unsigned char silence[12] = { 0x9e,0x8d,0x32,0x88,0x26,0x1a,0x3f,0x61,0xe8,0x70,0x4f,0x93 }; static unsigned short crc_tabccitt[256] = { 0x0000,0x1189,0x2312,0x329b,0x4624,0x57ad,0x6536,0x74bf, @@ -1783,6 +1733,13 @@ static unsigned short crc_tabccitt[256] = { 0x7bc7,0x6a4e,0x58d5,0x495c,0x3de3,0x2c6a,0x1ef1,0x0f78 }; +static void CleanCall(std::string &callsign) { + for (auto it=callsign.begin(); it!=callsign.end(); it++) + if (islower(*it)) + *it = toupper(*it); + callsign.resize(CALL_SIZE, ' '); +} + static void calcPFCS(unsigned char packet[58])//Netzwerk CRC { unsigned short crc_dstar_ffff = 0xffff; @@ -1803,198 +1760,103 @@ static void calcPFCS(unsigned char packet[58])//Netzwerk CRC return; } -bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%d]\n", path, value); - return true; -} - -bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%lg]\n", path, value); - return true; -} - -bool get_value(const Config &cfg, const char *path, bool &value, bool default_value) -{ - if (! cfg.lookupValue(path, value)) - value = default_value; - printf("%s = [%s]\n", path, value ? "true" : "false"); - return true; -} - -bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) -{ - if (cfg.lookupValue(path, value)) { - int l = value.length(); - if (lmax) { - printf("%s='%s' is wrong size, must be between %d and %d\n", path, value.c_str(), min, max); - return false; - } - } else - value = default_value; - printf("%s = [%s]\n", path, value.c_str()); - return true; -} - /* process configuration file */ -static int read_config(const char *cfgFile) +static bool ReadConfig(const char *cfgFile) { - int i; - Config cfg; + CQnetConfigure cfg; printf("Reading file %s\n", cfgFile); - // Read the file. If there is an error, report it and exit. - try { - cfg.readFile(cfgFile); - } - catch(const FileIOException &fioex) { - printf("Can't read %s\n", cfgFile); - return 1; - } - catch(const ParseException &pex) { - printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); - return 1; - } - - std::string path, value; - for (i=0; i<3; i++) { - path = "module."; - path += ('a' + i); - if (cfg.lookupValue(path + ".type", value)) { - if (0 == strcasecmp(value.c_str(), "dvrptr")) + if (cfg.Initialize(cfgFile)) + return true; + + const std::string estr; // an empty string + std::string type; + std::string path("module_"); + if (0 > assigned_module) { + // we need to find the lone dvrptr module + for (int i=0; i<3; i++) { + std::string test(path); + test.append(1, 'a'+i); + if (cfg.KeyExists(test)) { + cfg.GetValue(test, estr, type, 1, 16); + if (type.compare("dvrptr")) + continue; // this ain't it! + path.assign(test); + assigned_module = i; break; - } - } - if (i >= 3) { - printf("dvrptr not defined in any module!\n"); - return 1; - } - DVRPTR_MOD = 'A' + i; - - if (cfg.lookupValue(std::string(path+".callsign").c_str(), value) || cfg.lookupValue("ircddb.login", value)) { - int l = value.length(); - if (l<3 || l>CALL_SIZE-2) { - printf("Call '%s' is invalid length!\n", value.c_str()); - return 1; - } else { - for (i=0; i assigned_module) { + fprintf(stderr, "Error: no 'dvrptr' module found\n!"); + return true; + } } else { - printf("%s.callsign is not defined!\n", path.c_str()); - return 1; - } - - if (cfg.lookupValue("ircddb.login", value)) { - int l = value.length(); - if (l<3 || l>CALL_SIZE-2) { - printf("Call '%s' is invalid length!\n", value.c_str()); - return 1; - } else { - for (i=0; i> 8; - get_value(cfg, std::string(path+".rqst_count").c_str(), RQST_COUNT, 6, 20, 10); + cfg.GetValue(path+"_rqst_count", type, RQST_COUNT, 6, 20); + + cfg.GetValue(path+"_rx_invert", type, RX_Inverse); + cfg.GetValue(path+"_tx_invert", type, TX_Inverse); - get_value(cfg, std::string(path+".inverse.rx").c_str(), RX_Inverse, true); - get_value(cfg, std::string(path+".inverse.tx").c_str(), TX_Inverse, true); + path.assign("timing_"); + cfg.GetValue(path+"timeout_remote_g2", estr, REMOTE_TIMEOUT, 1, 10); + + cfg.GetValue(path+"play_delay", estr, DELAY_BETWEEN, 10, 25); + DELAY_BETWEEN *= 1000; inactiveMax = (REMOTE_TIMEOUT * 1000000) / 400; printf("... computed max number of loops %d, each loop is 400 microseconds\n", inactiveMax); @@ -2004,26 +1866,24 @@ static int read_config(const char *cfgFile) char *cleanstr (char *Text) { - unsigned int x = 0; - strcpy (Ergebnis,""); + static char cbuf[250]; + memset(cbuf, 0U, 250U); - for (x = 0; x < strlen (Text); x++) { - if ((Text[x] > 0x1F) && (Text[x] < 0x7F)) { - if (Text[x] == 0x27) { - strncat (Ergebnis,"\\",1); + for (unsigned int x=0; x < strlen(Text); x++) { + if ((Text[x] > 0x1F) && (Text[x] < 0x7F)) { + if (Text[x] == '\'') { + strcat(cbuf, "\\"); } - strncat (Ergebnis,&Text[x],1); + strncat(cbuf, &Text[x], 1); } } - for (x = 0; x < strlen(Ergebnis); x++) { - if (!isdigit(Ergebnis[x]) && - !isupper(Ergebnis[x]) && - (Ergebnis[x] != '/')) - Ergebnis[x] = ' '; + for (unsigned int x=0; x < strlen(cbuf); x++) { + if (!isdigit(cbuf[x]) && !isupper(cbuf[x]) && (cbuf[x] != '/')) + cbuf[x] = ' '; } - return (Ergebnis); + return (cbuf); } int open_port(char *dvrptr_device) @@ -2104,7 +1964,7 @@ static void send_ack(char *a_call, float ber) memcpy(Send_Modem_Header + 35, DVCALL_and_MOD, 8); memcpy(Send_Modem_Header + 43, "RPTR", 4); - if (memcmp(RPTR, DVCALL, RPTR_SIZE) != 0) { + if (memcmp(RPTR, DVCALL, CALL_SIZE) != 0) { memcpy(Send_Modem_Header + 11, RPTR, 7); memcpy(Send_Modem_Header + 19, RPTR, 7); @@ -2240,8 +2100,6 @@ static void send_ack(char *a_call, float ber) static void readFrom20000() { - struct sockaddr_in from; - socklen_t fromlen; int len; fd_set readfd; struct timeval tv; @@ -2251,57 +2109,55 @@ static void readFrom20000() unsigned char ctrl_in = 0x80; bool written_to_q = false; + int fd = Gate2Modem.GetFD(); while (true) { written_to_q = false; tv.tv_sec = 0; tv.tv_usec = 400; // If you change that, then change the inactiveMax computation - fromlen = sizeof(struct sockaddr); FD_ZERO (&readfd); - FD_SET (insock, &readfd); - select(insock + 1, &readfd, NULL, NULL, &tv); - if (FD_ISSET(insock, &readfd)) { - len = recvfrom (insock, (char *)&recv_buf, 58, 0, (struct sockaddr *)&from, &fromlen); - if (len == 58) { + FD_SET (fd, &readfd); + select(fd + 1, &readfd, NULL, NULL, &tv); + if (FD_ISSET(fd, &readfd)) { + len = Gate2Modem.Read(recv_buf.title, 56); + if (len == 56) { if (busy20000) { - FD_CLR (insock, &readfd); + FD_CLR (fd, &readfd); continue; } /* check the module and gateway */ - if (recv_buf.rf_hdr.rpt2[7] != DVRPTR_MOD) { - FD_CLR (insock, &readfd); + if (recv_buf.hdr.rpt1[7] != DVRPTR_MOD) { + fprintf(stderr, "rpt1=%.8s != %c, ignoring\n", recv_buf.hdr.rpt1, DVRPTR_MOD); + FD_CLR (fd, &readfd); break; } - memcpy(recv_buf.rf_hdr.rpt1, DVCALL_and_G, 8); + memcpy(recv_buf.hdr.rpt2, DVCALL_and_G, 8); - if (memcmp(RPTR, DVCALL, RPTR_SIZE) != 0) { - memcpy(recv_buf.rf_hdr.rpt1, RPTR, 7); - memcpy(recv_buf.rf_hdr.rpt2, RPTR, 7); + if (memcmp(RPTR, DVCALL, CALL_SIZE) != 0) { + memcpy(recv_buf.hdr.rpt1, RPTR, 7); + memcpy(recv_buf.hdr.rpt2, RPTR, 7); /* Do not change the user callsign if it is NOT an ack back from g2, because we want to know who is talking on remote net */ - if (memcmp(recv_buf.rf_hdr.mycall, DVCALL, 7) == 0) - memcpy(recv_buf.rf_hdr.mycall, RPTR, 7); + if (memcmp(recv_buf.hdr.mycall, DVCALL, 7) == 0) + memcpy(recv_buf.hdr.mycall, RPTR, 7); } - if ((recv_buf.rf_hdr.flags[0] != 0x00) && - (recv_buf.rf_hdr.flags[0] != 0x01) && - (recv_buf.rf_hdr.flags[0] != 0x08) && - (recv_buf.rf_hdr.flags[0] != 0x20) && - (recv_buf.rf_hdr.flags[0] != 0x28) && - (recv_buf.rf_hdr.flags[0] != 0x40)) { - FD_CLR (insock, &readfd); + if ((recv_buf.hdr.flag[0] != 0x00) && + (recv_buf.hdr.flag[0] != 0x01) && + (recv_buf.hdr.flag[0] != 0x08) && + (recv_buf.hdr.flag[0] != 0x20) && + (recv_buf.hdr.flag[0] != 0x28) && + (recv_buf.hdr.flag[0] != 0x40)) { + FD_CLR (fd, &readfd); break; } - if ((memcmp(recv_buf.pkt_id, "DSTR", 4) != 0) || - (recv_buf.flags[0] != 0x73) || - (recv_buf.flags[1] != 0x12) || - (recv_buf.myicm.icm_id != 0x20)) { - FD_CLR (insock, &readfd); + if ((memcmp(recv_buf.title, "DSVT", 4) != 0) || (recv_buf.id != 0x20)) { + FD_CLR (fd, &readfd); break; } @@ -2310,8 +2166,7 @@ static void readFrom20000() ctrl_in = 0x80; written_to_q = true; - streamid[0] = recv_buf.myicm.streamid[0]; - streamid[1] = recv_buf.myicm.streamid[1]; + streamid = recv_buf.streamid; Send_Modem_Header[0] =0xd0; Send_Modem_Header[1] =0x2f; @@ -2322,27 +2177,25 @@ static void readFrom20000() Send_Modem_Header[6] =0x00; Send_Modem_Header[7] =0x00; - if (recv_buf.rf_hdr.flags[0] != 0x01) { - if (recv_buf.rf_hdr.flags[0] == 0x00) - recv_buf.rf_hdr.flags[0] = 0x40; - else if (recv_buf.rf_hdr.flags[0] == 0x08) - recv_buf.rf_hdr.flags[0] = 0x48; - else if (recv_buf.rf_hdr.flags[0] == 0x20) - recv_buf.rf_hdr.flags[0] = 0x60; - else if (recv_buf.rf_hdr.flags[0] == 0x28) - recv_buf.rf_hdr.flags[0] = 0x68; + if (recv_buf.hdr.flag[0] != 0x1U) { + if (recv_buf.hdr.flag[0] == 0x0U) + recv_buf.hdr.flag[0] = 0x40U; + else if (recv_buf.hdr.flag[0] == 0x8U) + recv_buf.hdr.flag[0] = 0x48U; + else if (recv_buf.hdr.flag[0] == 0x20U) + recv_buf.hdr.flag[0] = 0x60U; + else if (recv_buf.hdr.flag[0] == 0x28U) + recv_buf.hdr.flag[0] = 0x68U; else - recv_buf.rf_hdr.flags[0] = 0x40; + recv_buf.hdr.flag[0] = 0x40U; } - recv_buf.rf_hdr.flags[1] = 0x00; - recv_buf.rf_hdr.flags[2] = 0x00; - - memcpy(Send_Modem_Header + 8, recv_buf.rf_hdr.flags, 3); - memcpy(Send_Modem_Header + 11, recv_buf.rf_hdr.rpt1, 8); - memcpy(Send_Modem_Header + 19, recv_buf.rf_hdr.rpt2, 8); - memcpy(Send_Modem_Header + 27, recv_buf.rf_hdr.urcall, 8); - memcpy(Send_Modem_Header + 35, recv_buf.rf_hdr.mycall, 8); - memcpy(Send_Modem_Header + 43, recv_buf.rf_hdr.sfx, 4); + recv_buf.hdr.flag[1] = recv_buf.hdr.flag[2] = 0x0; + memcpy(Send_Modem_Header + 8, recv_buf.hdr.flag, 3); + memcpy(Send_Modem_Header + 11, recv_buf.hdr.rpt1, 8); + memcpy(Send_Modem_Header + 19, recv_buf.hdr.rpt2, 8); + memcpy(Send_Modem_Header + 27, recv_buf.hdr.urcall, 8); + memcpy(Send_Modem_Header + 35, recv_buf.hdr.mycall, 8); + memcpy(Send_Modem_Header + 43, recv_buf.hdr.sfx, 4); Send_Modem_Header[47] =0xE1; Send_Modem_Header[48] =0x00; Send_Modem_Header[49] =0x00; @@ -2351,11 +2204,7 @@ static void readFrom20000() // printf("\nNetwork Header ID: %2.2x\n",seq_no2); block = 0; - printf("From G2: streamid=%d,%d, flags=%02x:%02x:%02x, myCall=%.8s/%.4s, yrCall=%.8s, rpt1=%.8s, rpt2=%.8s\n", - recv_buf.myicm.streamid[0], recv_buf.myicm.streamid[1], - recv_buf.rf_hdr.flags[0], recv_buf.rf_hdr.flags[1], recv_buf.rf_hdr.flags[2], - recv_buf.rf_hdr.mycall, recv_buf.rf_hdr.sfx, recv_buf.rf_hdr.urcall, - recv_buf.rf_hdr.rpt2, recv_buf.rf_hdr.rpt1); + printf("From G2: streamid=%04x, flags=%02x:%02x:%02x, myCall=%.8s/%.4s, yrCall=%.8s, rpt1=%.8s, rpt2=%.8s\n", ntohs(recv_buf.streamid), recv_buf.hdr.flag[0], recv_buf.hdr.flag[1], recv_buf.hdr.flag[2], recv_buf.hdr.mycall, recv_buf.hdr.sfx, recv_buf.hdr.urcall, recv_buf.hdr.rpt1, recv_buf.hdr.rpt2); ptt_off[4] = seq_no2; start_Header[4]=seq_no2; @@ -2367,8 +2216,8 @@ static void readFrom20000() write(fd_ser, Send_Modem_Header, sizeof (Send_Modem_Header)); inactive = 0; seq_no = 0; - } else if (len == 29) { - seq_no = recv_buf.myicm.ctrl & 0x1f; + } else if (len == 27) { + seq_no = recv_buf.ctrl & 0x1f; if ((seq_no < 3) && (old_seq_no > 17)) { block ++; if (block >= 12) @@ -2378,12 +2227,12 @@ static void readFrom20000() seq_no = block * 21 + seq_no; if (busy20000) { - if ((recv_buf.myicm.streamid[0] == streamid[0]) && (recv_buf.myicm.streamid[1] == streamid[1])) { - if ((recv_buf.myicm.ctrl <= ctrl_in) && (ctrl_in != 0x80)) { + if (recv_buf.streamid == streamid) { + if ((recv_buf.ctrl <= ctrl_in) && (ctrl_in != 0x80)) { /* do not update written_to_q, ctrl_in */ ; // printf("dup\n"); } else { - ctrl_in = recv_buf.myicm.ctrl; + ctrl_in = recv_buf.ctrl; if (ctrl_in == 0x14) ctrl_in = 0x80; @@ -2397,8 +2246,8 @@ static void readFrom20000() writevoice[5] =seq_no; writevoice[6] =0x00; writevoice[7] =0x00; - ambefec_regenerate((recv_buf.rf_audio.buff)); - memcpy(writevoice + 8, recv_buf.rf_audio.buff, 12); + ambefec_regenerate(recv_buf.vasd.voice); + memcpy(writevoice + 8, recv_buf.vasd.voice, 12); writevoice[20] =0x00; writevoice[21] =0x00; writevoice[22] =0x00; @@ -2408,33 +2257,32 @@ static void readFrom20000() write(fd_ser, writevoice, sizeof (writevoice)); inactive = 1; - if ((recv_buf.myicm.ctrl & 0x40) != 0) { - printf("End G2: streamid=%d,%d\n",recv_buf.myicm.streamid[0], recv_buf.myicm.streamid[1]); + if ((recv_buf.ctrl & 0x40) != 0) { + printf("End G2: streamid=%04x\n",ntohs(recv_buf.streamid)); ptt_off[4] = Send_Modem_Header[4]; write(fd_ser, ptt_off, 8); busy20000 = false; - streamid[0] = 0x00; - streamid[1] = 0x00; + streamid = 0x0U; inactive = 0; - FD_CLR (insock, &readfd); + FD_CLR (fd, &readfd); break; } } } } else { - FD_CLR (insock, &readfd); + FD_CLR (fd, &readfd); break; } } else { if (!busy20000) { - FD_CLR (insock, &readfd); + FD_CLR (fd, &readfd); break; } } - FD_CLR (insock, &readfd); + FD_CLR (fd, &readfd); } /* @@ -2449,8 +2297,7 @@ static void readFrom20000() write(fd_ser, ptt_off, 8); busy20000 = false; - streamid[0] = 0x00; - streamid[1] = 0x00; + streamid = 0x0U; inactive = 0; break; @@ -2542,7 +2389,7 @@ bool check_serial() puffer[1] = 0x00; sprintf(temp_dvrptr_serial, "%02X.%02X.%02X.%02X", puffer[4], puffer[5], puffer[6], puffer[7]); printf("Device %s has serial=[%s]\n", dvrptr_device, temp_dvrptr_serial); - if (strcmp(temp_dvrptr_serial, DVRPTR_SERIAL) == 0) { + if (strcmp(temp_dvrptr_serial, DVRPTR_SERIAL.c_str()) == 0) { printf("Device %s serial number matches DVRPTR_SERIAL in dvrptr.cfg\n", dvrptr_device); match = true; } @@ -2577,8 +2424,6 @@ int main(int argc, const char **argv) int InitCount = 1; short seq_no = 0; unsigned char puffer[200]; - struct sockaddr_in inaddr; - int rc; char Temp_Text[200]; time_t last_RF_time = 0; time_t tNow = 0; @@ -2587,42 +2432,71 @@ int main(int argc, const char **argv) int fw_version; char fw_string[10]; - char *temp_ptr = NULL; - setvbuf(stdout, NULL, _IOLBF, 0); - printf("dvrptr VERSION %s\n", VERSION); + printf("dvrptr VERSION %s\n", DVRPTR_VERSION); if (argc != 2) { - printf("Usage: ./dvrptr dvrptr.cfg\n"); + fprintf(stderr, "Usage: %s dvrptr.cfg\n", argv[0]); + return 1; + } + + if ('-' == argv[1][0]) { + printf("\nQnetDVRPTR Version #%s Copyright (C) 2018-2019 by Thomas A. Early N7TAE\n", DVRPTR_VERSION); + printf("QnetDVRPTR comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); + printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n"); + return 0; + } + + const char *qn = strstr(argv[0], "qndvrptr"); + if (NULL == qn) { + fprintf(stderr, "Error finding 'qndvrptr' in %s!\n", argv[0]); + return 1; + } + qn += 8; + + switch (*qn) { + case NULL: + assigned_module = -1; + break; + case 'a': + assigned_module = 0; + break; + case 'b': + assigned_module = 1; + break; + case 'c': + assigned_module = 2; + break; + default: + fprintf(stderr, "ERROR: '%s' is not a valid module\nassigned module must be a, b or c\n", argv[1]); return 1; } - rc = read_config(argv[1]); - if (rc != 0) { - printf("Failed to process config file %s\n", argv[1]); + if (ReadConfig(argv[1])) { + fprintf(stderr, "Failed to process config file %s\n", argv[2]); return 1; } if (!check_serial()) { - printf("Cant find any FREE ACMx device that matches\n"); + fprintf(stderr, "Cant find any FREE ACMx device that matches\n"); return 1; } if (strlen(DVCALL) != 8) { - printf("Bad DVCALL value, length must be exactly 8 bytes\n"); + fprintf(stderr, "Bad DVCALL value, length must be exactly 8 bytes\n"); return 1; } if ((DVRPTR_MOD != 'A') && (DVRPTR_MOD != 'B') && (DVRPTR_MOD != 'C')) { - printf("Bad DVCALL_MOD value, must be one of A or B or C\n"); + fprintf(stderr, "Bad DVCALL_MOD value, must be one of A or B or C\n"); return 1; } if (DVRPTR_MOD == 'A') - SND_TERM_ID = 0x03; + SND_TERM_ID = 0x3; else if (DVRPTR_MOD == 'B') - SND_TERM_ID = 0x01; + SND_TERM_ID = 0x1; else if (DVRPTR_MOD == 'C') - SND_TERM_ID = 0x02; + SND_TERM_ID = 0x2; strcpy(DVCALL_and_G, DVCALL); DVCALL_and_G[7] = 'G'; @@ -2630,29 +2504,9 @@ int main(int argc, const char **argv) strcpy(DVCALL_and_MOD, DVCALL); DVCALL_and_MOD[7] = DVRPTR_MOD; - insock = socket(PF_INET, SOCK_DGRAM, 0); - if (insock == -1) { - printf("Failed to create insock, error=%d, message=%s\n",errno,strerror(errno)); + Modem2Gate.SetUp(modem2gate.c_str()); + if (Gate2Modem.Open(gate2modem.c_str())) return 1; - } - - memset(&inaddr, 0, sizeof(inaddr)); - inaddr.sin_family = AF_INET; - inaddr.sin_port = htons(DVRPTR_INTERNAL_PORT); - inaddr.sin_addr.s_addr = inet_addr(DVRPTR_INTERNAL_IP); - rc = bind(insock, (struct sockaddr *)&inaddr, sizeof(inaddr)); - if (rc == -1) { - printf("bind to socket failed, error=%d, message=%s\n", errno,strerror(errno)); - close(insock); - insock = -1; - return 1; - } - fcntl(insock,F_SETFL,O_NONBLOCK); - - memset(&outaddr, 0, sizeof(outaddr)); - outaddr.sin_family = AF_INET; - outaddr.sin_port = htons(GATEWAY_PORT); - outaddr.sin_addr.s_addr = inet_addr(GATEWAY_IP); if (RX_Inverse == true) { Modem_Init2[6]=0x01; @@ -2665,18 +2519,10 @@ int main(int argc, const char **argv) Modem_Init2[6]=0x02; } - memcpy(S_packet, "DSTR", 4); - S_packet[4] = 0x00; - S_packet[5] = 0x00; - S_packet[6] = 0x73; - S_packet[7] = 0x21; - S_packet[8] = 0x00; - S_packet[9] = 0x10; - dstar_dv_init(); time(&tNow); - srand(tNow + getpid()); + CRandom Random; time(&time_rqst); @@ -2696,19 +2542,6 @@ int main(int argc, const char **argv) } } - /* send the S packet if needed */ - if ((tNow - S_ctrl_msg_time) > 60) { - S_packet[5] = (unsigned char)(C_COUNTER & 0xff); - S_packet[4] = ((C_COUNTER >> 8) & 0xff); - memcpy(S_packet + 10, DVCALL, 8); - S_packet[17] = 'S'; - memcpy(S_packet + 18, DVCALL, 8); - S_packet[25] = 'S'; - sendto(insock, (char *)S_packet, sizeof(S_packet), 0, (struct sockaddr *)&outaddr, sizeof(outaddr)); - C_COUNTER ++; - S_ctrl_msg_time = tNow; - } - // init Modem if (InitCount == 4 ) { write(fd_ser, Modem_STATUS, sizeof (Modem_STATUS)); @@ -2794,13 +2627,6 @@ int main(int argc, const char **argv) printf("From RF: flags=%02x:%02x:%02x, myCall=%s/%s, yrCall=%s, rpt1=%s, rpt2=%s\n", puffer[8], puffer[9], puffer[10], myCall, myCall2, myUR, myRPT1, myRPT2); - /* do not allow connections to stupid/bad STN programs */ - temp_ptr = strstr(myUR, INVALID_YRCALL_KEY); - if (temp_ptr == myUR) { - printf("YRCALL value [%s] starts with the INVALID_YRCALL_KEY [%s], resetting to CQCQCQ\n", myUR, INVALID_YRCALL_KEY); - memcpy(myUR, "CQCQCQ ", 8); - } - /* If rpt1 is equal to rpt2, but the first 7 bytes is not our system, then a remote repeater(standalone) is trying to reach our repeater over RF, @@ -2826,15 +2652,20 @@ int main(int argc, const char **argv) (myRPT2[7] == 'C') || (myRPT2[7] == 'G')) memcpy(myRPT2, DVCALL, 7); - else + else { + printf("myRPT2=%.8s not A, B, C or G, blanking it\n", myRPT2); memset(myRPT2, ' ', 8); + } - if ((memcmp(myUR, "CQCQCQ", 6) != 0) && (myRPT2[0] != ' ')) + if ((memcmp(myUR, "CQCQCQ", 6) != 0) && (myRPT2[0] != ' ')) { memcpy(myRPT2, DVCALL_and_G, 8); + } /* 8th in rpt1, rpt2 must be diff */ - if (myRPT2[7] == myRPT1[7]) + if (myRPT2[7] == myRPT1[7]) { memset(myRPT2, ' ', 8); + printf("%.8s==%.8s, blanking myRPT2\n", myRPT2, myRPT1); + } /* Are we restricting the RF user ? @@ -2843,8 +2674,8 @@ int main(int argc, const char **argv) that means that mycall, rpt1, rpt2 must be equal to RPTR otherwise we drop the rf data */ - if (memcmp(RPTR, DVCALL, RPTR_SIZE) != 0) { - if (memcmp(myCall, RPTR, RPTR_SIZE) != 0) { + if (memcmp(RPTR, DVCALL, CALL_SIZE) != 0) { + if (memcmp(myCall, RPTR, CALL_SIZE) != 0) { printf("mycall=[%.8s], not equal to %s\n", myCall, RPTR); ok = false; } @@ -2900,19 +2731,6 @@ int main(int argc, const char **argv) } } - S_packet[5] = (unsigned char)(C_COUNTER & 0xff); - S_packet[4] = ((C_COUNTER >> 8) & 0xff); - memcpy(S_packet + 10, myCall, 8); - memcpy(S_packet + 18, DVCALL, 8); - S_packet[25] = DVRPTR_MOD; - - if (ok) { - if (IS_ENABLED) { - sendto(insock, (char *)S_packet, sizeof(S_packet), 0, (struct sockaddr *)&outaddr, sizeof(outaddr)); - C_COUNTER ++; - } - } - /* Before we send the data to the local gateway, set RPT1, RPT2 to be the local gateway @@ -2921,47 +2739,41 @@ int main(int argc, const char **argv) if (myRPT2[7] != ' ') memcpy(myRPT2, DVCALL, 7); - memcpy(Send_Network_Header , "DSTR", 4); - Send_Network_Header[4] = ((C_COUNTER >> 8) & 0xff); - Send_Network_Header[5] = (unsigned char)(C_COUNTER & 0xff); - Send_Network_Header[6] = 0x73; - Send_Network_Header[7] = 0x12; - Send_Network_Header[8] = 0x00; - Send_Network_Header[9] = 0x30; - Send_Network_Header[10] = 0x20; - Send_Network_Header[11] = 0x00; - Send_Network_Header[12] = 0x01; - Send_Network_Header[13] = SND_TERM_ID; - streamid_raw = (::rand() % 65535U) + 1U; - Send_Network_Header[14] = streamid_raw / 256U; - Send_Network_Header[15] = streamid_raw % 256U; - Send_Network_Header[16] = 0x80; + memcpy(Send_Network_Header.title , "DSVT", 4); + Send_Network_Header.config = 0x10U; + memset(Send_Network_Header.flaga, 0U, 3U); + Send_Network_Header.id = 0x20U; + Send_Network_Header.flagb[0] = 0x0U; + Send_Network_Header.flagb[1] = 0x1U; + Send_Network_Header.flagb[2] = SND_TERM_ID; + streamid_raw = Random.NewStreamID(); + Send_Network_Header.streamid = streamid_raw; + Send_Network_Header.ctrl = 0x80U; seq_no = 0; - if (puffer[8] == 0x40) - Send_Network_Header[17] = 0x00; - else if (puffer[8] == 0x48) - Send_Network_Header[17] = 0x08; - else if (puffer[8] == 0x60) - Send_Network_Header[17] = 0x20; - else if (puffer[8] == 0x68) - Send_Network_Header[17] = 0x28; + if (puffer[8] == 0x40U) + Send_Network_Header.hdr.flag[0] = 0x0U; + else if (puffer[8] == 0x48U) + Send_Network_Header.hdr.flag[0] = 0x8U; + else if (puffer[8] == 0x60U) + Send_Network_Header.hdr.flag[0] = 0x20U; + else if (puffer[8] == 0x68U) + Send_Network_Header.hdr.flag[0] = 0x28U; else - Send_Network_Header[17] = 0x00; + Send_Network_Header.hdr.flag[0] = 0x00; - Send_Network_Header[18] = puffer[9]; - Send_Network_Header[19] = puffer[10]; - memcpy(Send_Network_Header + 20, myRPT2, 8); - memcpy(Send_Network_Header + 28, myRPT1, 8); - memcpy(Send_Network_Header + 36, myUR, 8); - memcpy(Send_Network_Header + 44, myCall, 8); - memcpy(Send_Network_Header + 52, myCall2, 4); - calcPFCS(Send_Network_Header); + Send_Network_Header.hdr.flag[1] = puffer[9]; + Send_Network_Header.hdr.flag[2] = puffer[10]; + memcpy(Send_Network_Header.hdr.rpt1, myRPT1, 8); + memcpy(Send_Network_Header.hdr.rpt2, myRPT2, 8); + memcpy(Send_Network_Header.hdr.urcall, myUR, 8); + memcpy(Send_Network_Header.hdr.mycall, myCall, 8); + memcpy(Send_Network_Header.hdr.sfx, myCall2, 4); + calcPFCS(Send_Network_Header.title); if (ok) { if (IS_ENABLED) { - sendto(insock, (char *)&Send_Network_Header, 58, 0, (struct sockaddr *)&outaddr, sizeof(outaddr)); - C_COUNTER ++; + Modem2Gate.Write(Send_Network_Header.title, 56); } } @@ -3009,28 +2821,22 @@ int main(int argc, const char **argv) if (last_RF_time > 0) time(&last_RF_time); - memcpy(Send_Network_Audio , "DSTR", 4); - Send_Network_Audio[4] = ((C_COUNTER >> 8) & 0xff); - Send_Network_Audio[5] = (unsigned char)(C_COUNTER & 0xff); - Send_Network_Audio[6] = 0X73; - Send_Network_Audio[7] = 0X12; - Send_Network_Audio[8] = 0X00; - Send_Network_Audio[9] = 0X13; - Send_Network_Audio[10] = 0X20; - Send_Network_Audio[11] = 0X00; - Send_Network_Audio[12] = 0X01; - Send_Network_Audio[13] = SND_TERM_ID; - Send_Network_Audio[14] = streamid_raw / 256U; - Send_Network_Audio[15] = streamid_raw % 256U; - Send_Network_Audio[16] = seq_no; - memcpy(Send_Network_Audio + 17 , puffer + 8, 12); + memcpy(Send_Network_Audio.title , "DSVT", 4); + Send_Network_Audio.config = 0x20U; + memset(Send_Network_Audio.flaga, 0U, 3U); + Send_Network_Audio.id = 0x20U; + Send_Network_Audio.flagb[0] = 0X00; + Send_Network_Audio.flagb[1] = 0X01; + Send_Network_Audio.flagb[2] = SND_TERM_ID; + Send_Network_Audio.streamid = streamid_raw; + Send_Network_Audio.ctrl = seq_no; + memcpy(Send_Network_Audio.vasd.voice , puffer + 8, 12); if (IS_ENABLED) { - sendto(insock, (char *)&Send_Network_Audio, 29, 0, (struct sockaddr *)&outaddr, sizeof(outaddr)); - ber_errs = dstar_dv_decode((unsigned char *)&Send_Network_Audio + 17, ber_data); + Modem2Gate.Write(Send_Network_Audio.title, 27); + ber_errs = dstar_dv_decode(Send_Network_Audio.vasd.voice, ber_data); num_bit_errors += ber_errs; - num_dv_frames ++; - C_COUNTER ++; + num_dv_frames++; } seq_no ++; @@ -3081,10 +2887,7 @@ int main(int argc, const char **argv) } } - if ((puffer[0] == 0xD0) && - (puffer[1] == 0x17) && - (puffer[2] == 0x00) && - (puffer[3] == 0x91)) { + if ((puffer[0] == 0xD0) && (puffer[1] == 0x17) && (puffer[2] == 0x00) && (puffer[3] == 0x91)) { rqst_count = RQST_COUNT; printf("DVRPTR Hardware ver: %.20s\n",puffer+6 ); @@ -3099,14 +2902,11 @@ int main(int argc, const char **argv) if ((fw_version & 0x0f) > 0) fw_string[4] = (fw_version & 0x0f) + 'a' - 1; - printf("DVRPTR Firmware ver: %.5s\n",fw_string ); + printf("DVRPTR Firmware ver: %.5s\n",fw_string); puffer[1] = 0x00; } - if ((puffer[0] == 0xD0) && - ((puffer[1] == 0x07) || (puffer[1] == 0x08)) && - (puffer[2] == 0x00) && - (puffer[3] == 0x90)) { + if ((puffer[0] == 0xD0) && ((puffer[1] == 0x07) || (puffer[1] == 0x08)) && (puffer[2] == 0x00) && (puffer[3] == 0x90)) { rqst_count = RQST_COUNT; printf("\n------- STATUS READ FROM MODEM ---------\n"); @@ -3122,10 +2922,7 @@ int main(int argc, const char **argv) } /* serial */ - if ((puffer[0] == 0xD0) && - (puffer[1] == 0x05) && - (puffer[2] == 0x00) && - (puffer[3] == 0x92)) { + if ((puffer[0] == 0xD0) && (puffer[1] == 0x05) && (puffer[2] == 0x00) && (puffer[3] == 0x92)) { rqst_count = RQST_COUNT; puffer[1] = 0x00; } @@ -3138,55 +2935,46 @@ int main(int argc, const char **argv) /* If an RF user TXed and disappeared after that */ if ((last_RF_time > 0) && ((tNow - last_RF_time) > 1)) { printf("End RF(Timeout), ber=%.02f\n", - (num_dv_frames == 0)?0.00:100.00 * ((float)num_bit_errors / (float)(num_dv_frames * 24.00)) ); + (num_dv_frames == 0) ? 0.0 : 100.0 * ((float)num_bit_errors / (float)(num_dv_frames * 24.0)) ); ptt_off[4] = Send_Modem_Header[4]; write(fd_ser, ptt_off, 8); } - if ( ((puffer[0] == 0xD0) && - (puffer[1] == 0x03) && - (puffer[2] == 0x00) && - (puffer[3] == 0x1A)) || - ((last_RF_time > 0) && ((tNow - last_RF_time) > 1)) ) { + if ( ((puffer[0] == 0xD0) && (puffer[1] == 0x03) && (puffer[2] == 0x00) && (puffer[3] == 0x1A)) || ((last_RF_time > 0) && ((tNow - last_RF_time) > 1)) ) { puffer[0] = 0x00; if (last_RF_time > 0) { - memcpy(Send_Network_Header , "DSTR", 4); - Send_Network_Header[4] = ((C_COUNTER >> 8) & 0xff); - Send_Network_Header[5] = (unsigned char)(C_COUNTER & 0xff); - Send_Network_Header[6] = 0x73; - Send_Network_Header[7] = 0x12; - Send_Network_Header[8] = 0x00; - Send_Network_Header[9] = 0x13; - Send_Network_Header[10] = 0x20; - Send_Network_Header[11] = 0x00; - Send_Network_Header[12] = 0x01; - Send_Network_Header[13] = 0x01; - Send_Network_Header[16] = seq_no | 0x40; - Send_Network_Header[17] = 0x40; - Send_Network_Header[18] = 0x55; - Send_Network_Header[19] = 0xc8; + const unsigned char silence[12] = { 0x9EU,0x8DU,0x32U,0x88U,0x26U,0x1AU,0x3FU,0x61U,0xE8U,0x70U,0x4FU,0x93U }; + const unsigned char silsync[12] = { 0x9EU,0x8DU,0x32U,0x88U,0x26U,0x1AU,0x3FU,0x61U,0xE8U,0x55U,0x2DU,0x16U }; + memcpy(Send_Network_Audio.title , "DSVT", 4); + Send_Network_Audio.config = 0x20U; + memset(Send_Network_Audio.flaga, 0U, 3U); + Send_Network_Audio.id = 0x20U; + Send_Network_Audio.flagb[0] = 0x0U; + Send_Network_Audio.flagb[1] = 0x1U; + Send_Network_Audio.flagb[2] = SND_TERM_ID; + Send_Network_Audio.ctrl = seq_no | 0x40U; + memcpy(Send_Network_Audio.vasd.voice, seq_no ? silsync : silence, 12U); if (ok) { if (IS_ENABLED) { - sendto(insock, (char *)&Send_Network_Header, 29, 0, (struct sockaddr *)&outaddr, sizeof(outaddr)); - C_COUNTER ++; + Modem2Gate.Write(Send_Network_Audio.title, 27); } } ptt_off[4] = Send_Modem_Header[4]; write(fd_ser, ptt_off, 8); - printf("End RF, ber=%.02f\n", (num_dv_frames == 0) ? 0.00 : 100.00 * ((float)num_bit_errors / (float)(num_dv_frames * 24.00)) ); + printf("End RF, ber=%.02f\n", (num_dv_frames == 0) ? 0.0 : 100.0 * ((float)num_bit_errors / (float)(num_dv_frames * 24.0)) ); last_RF_time = 0; if (IS_ENABLED && RPTR_ACK) - send_ack(ok ? myCall : RPTR, (num_dv_frames == 0) ? 0.00 : 100.00 * ((float)num_bit_errors / (float)(num_dv_frames * 24.00)) ); + send_ack(ok ? myCall : RPTR, (num_dv_frames == 0) ? 0.0 : 100.0 * ((float)num_bit_errors / (float)(num_dv_frames * 24.0)) ); ok = false; } } } - close(insock); + Gate2Modem.Close(); printf("dvrptr exiting...\n"); return 0; diff --git a/QnetGateway.cpp b/QnetGateway.cpp index d292dca..a5482f9 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -1,6 +1,6 @@ /* * Copyright (C) 2010 by Scott Lawson KI4LKF - * Copyright (C) 2017-2018 by Thomas Early N7TAE + * Copyright (C) 2017-2019 by Thomas Early N7TAE * * 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 @@ -17,14 +17,6 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -/* by KI4LKF, N7TAE */ -/* - QnetGateway is a dstar G2 gateway, using irc routing - adapted from the OpenG2 G2 gateway - Version 2.61 or higher will use ONLY the irc mechanism of routing - and it will NOT use any local Postgres databases or any TRUST(s) -*/ - #include #include #include @@ -44,21 +36,20 @@ #include #include #include -#include +#include #include #include #include #include #include -#include #include "IRCDDB.h" #include "IRCutils.h" -#include "versions.h" -#include "QnetTypeDefs.h" +#include "QnetConfigure.h" #include "QnetGateway.h" +const std::string IRCDDB_VERSION("QnetGateway-9.2"); extern void dstar_dv_init(); extern int dstar_dv_decode(const unsigned char *d, int data[3]); @@ -75,9 +66,59 @@ static void sigCatch(int signum) return; } +int CQnetGateway::FindIndex(const int i) const +{ + if (i<0 || i > 2) + return -1; + int index = Index[i]; + if (index < 0) { + if (AF_INET == link_family[i]) { + index = ii[1] ? 1 : 0; + } else if (AF_INET6 == link_family[i]) { + index = 0; + } + } + return index; +} + +bool CQnetGateway::VoicePacketIsSync(const unsigned char *text) +{ + return *text==0x55U && *(text+1)==0x2DU && *(text+2)==0x16U; +} + +void CQnetGateway::UnpackCallsigns(const std::string &str, std::set &set, const std::string &delimiters) +{ + std::string::size_type lastPos = str.find_first_not_of(delimiters, 0); // Skip delimiters at beginning. + std::string::size_type pos = str.find_first_of(delimiters, lastPos); // Find first non-delimiter. + + while (std::string::npos != pos || std::string::npos != lastPos) { + std::string element = str.substr(lastPos, pos-lastPos); + if (element.length()>=3 && element.length()<=8) { + ToUpper(element); + element.resize(CALL_SIZE, ' '); + set.insert(element); // Found a token, add it to the list. + } else + fprintf(stderr, "found bad callsign in list: %s\n", str.c_str()); + lastPos = str.find_first_not_of(delimiters, pos); // Skip delimiters. + pos = str.find_first_of(delimiters, lastPos); // Find next non-delimiter. + } +} + +void CQnetGateway::PrintCallsigns(const std::string &key, const std::set &set) +{ + printf("%s = [", key.c_str()); + for (auto it=set.begin(); it!=set.end(); it++) { + if (it != set.begin()) + printf(","); + printf("%s", (*it).c_str()); + } + printf("]\n"); +} + + void CQnetGateway::set_dest_rptr(int mod_ndx, char *dest_rptr) { - FILE *statusfp = fopen(status_file.c_str(), "r"); + FILE *statusfp = fopen(FILE_STATUS.c_str(), "r"); if (statusfp) { setvbuf(statusfp, (char *)NULL, _IOLBF, 0); @@ -170,220 +211,168 @@ void CQnetGateway::calcPFCS(unsigned char *packet, int len) return; } -bool CQnetGateway::get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%d]\n", path, value); - return true; -} - -bool CQnetGateway::get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%lg]\n", path, value); - return true; -} - -bool CQnetGateway::get_value(const Config &cfg, const char *path, bool &value, bool default_value) -{ - if (! cfg.lookupValue(path, value)) - value = default_value; - printf("%s = [%s]\n", path, value ? "true" : "false"); - return true; -} - -bool CQnetGateway::get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) -{ - if (cfg.lookupValue(path, value)) { - int l = value.length(); - if (lmax) { - printf("%s is invalid\n", path); - return false; - } - } else - value = default_value; - printf("%s = [%s]\n", path, value.c_str()); - return true; -} - /* process configuration file */ -bool CQnetGateway::read_config(char *cfgFile) +bool CQnetGateway::ReadConfig(char *cfgFile) { - Config cfg; - - printf("Reading file %s\n", cfgFile); - // Read the file. If there is an error, report it and exit. - try { - cfg.readFile(cfgFile); - } catch(const FileIOException &fioex) { - printf("Can't read %s\n", cfgFile); + const std::string estr; // an empty string + CQnetConfigure cfg; + if (cfg.Initialize(cfgFile)) return true; - } catch(const ParseException &pex) { - printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); - return true; - } - if (! get_value(cfg, "ircddb.login", owner, 3, CALL_SIZE-2, "UNDEFINED")) + // ircddb + std::string path("ircddb_login"); + if (cfg.GetValue(path, estr, owner, 3, CALL_SIZE-2)) return true; OWNER = owner; ToLower(owner); ToUpper(OWNER); - printf("OWNER=[%s]\n", OWNER.c_str()); + printf("OWNER='%s'\n", OWNER.c_str()); OWNER.resize(CALL_SIZE, ' '); - for (short int m=0; m<3; m++) { - std::string path = "module."; - path += m + 'a'; + path.assign("ircddb"); + for (int i=0; i<2; i++) { + std::string p(path + std::to_string(i) + "_"); + cfg.GetValue(p+"host", estr, ircddb[i].ip, 0, MAXHOSTNAMELEN); + cfg.GetValue(p+"port", estr, ircddb[i].port, 1000, 65535); + cfg.GetValue(p+"password", estr, IRCDDB_PASSWORD[i], 0, 512); + } + if (0 == ircddb[0].ip.compare(ircddb[1].ip)) { + fprintf(stderr, "IRC networks must be different\n"); + return true; + } + + // module + for (int m=0; m<3; m++) { + path.assign("module_"); + path.append(1, 'a' + m); std::string type; - if (cfg.lookupValue(std::string(path+".type").c_str(), type)) { - if (strcasecmp(type.c_str(), "dvap") && strcasecmp(type.c_str(), "dvrptr") && strcasecmp(type.c_str(), "mmdvm")) { + if (cfg.GetValue(path, estr, type, 1, 16)) { + rptr.mod[m].defined = false; + } else { + printf("Found Module: %s = '%s'\n", path.c_str(), type.c_str()); + if (0 == type.compare("dvap")) { rptr.mod[m].package_version.assign(IRCDDB_VERSION+".DVAP"); } + else if (0 == type.compare("dvrptr")) { rptr.mod[m].package_version.assign(IRCDDB_VERSION+".DVRPTR"); } + else if (0 == type.compare("mmdvmhost")) { rptr.mod[m].package_version.assign(IRCDDB_VERSION+".Relay"); } + else if (0 == type.compare("mmdvmmodem")) { rptr.mod[m].package_version.assign(IRCDDB_VERSION+".Modem"); } + else if (0 == type.compare("itap")) { rptr.mod[m].package_version.assign(IRCDDB_VERSION+".ITAP"); } + else { printf("module type '%s' is invalid\n", type.c_str()); return true; } rptr.mod[m].defined = true; - if (0 == strcasecmp(type.c_str(), "dvap")) - rptr.mod[m].package_version = DVAP_VERSION; - else if (0 == strcasecmp(type.c_str(), "dvrptr")) - rptr.mod[m].package_version = DVRPTR_VERSION; - else - rptr.mod[m].package_version = MMDVM_VERSION; - if (! get_value(cfg, std::string(path+".ip").c_str(), rptr.mod[m].portip.ip, 7, IP_SIZE, "127.0.0.1")) - return true; - get_value(cfg, std::string(path+".port").c_str(), rptr.mod[m].portip.port, 16000, 65535, 19998+m); - get_value(cfg, std::string(path+".frequency").c_str(), rptr.mod[m].frequency, 0.0, 1.0e12, 0.0); - get_value(cfg, std::string(path+".offset").c_str(), rptr.mod[m].offset,-1.0e12, 1.0e12, 0.0); - get_value(cfg, std::string(path+".range").c_str(), rptr.mod[m].range, 0.0, 1609344.0, 0.0); - get_value(cfg, std::string(path+".agl").c_str(), rptr.mod[m].agl, 0.0, 1000.0, 0.0); - get_value(cfg, std::string(path+".latitude").c_str(), rptr.mod[m].latitude, -90.0, 90.0, 0.0); - get_value(cfg, std::string(path+".longitude").c_str(), rptr.mod[m].longitude, -180.0, 180.0, 0.0); - if (! cfg.lookupValue(path+".desc1", rptr.mod[m].desc1)) - rptr.mod[m].desc1 = ""; - if (! cfg.lookupValue(path+".desc2", rptr.mod[m].desc2)) - rptr.mod[m].desc2 = ""; - if (! get_value(cfg, std::string(path+".url").c_str(), rptr.mod[m].url, 0, 80, "github.com/n7tae/QnetGateway")) - return true; - // truncate strings - if (rptr.mod[m].desc1.length() > 20) - rptr.mod[m].desc1.resize(20); - if (rptr.mod[m].desc2.length() > 20) - rptr.mod[m].desc2.resize(20); + + path.append(1, '_'); + if (cfg.KeyExists(path+"tx_frequency")) { + cfg.GetValue(path+"tx_frequency", type, rptr.mod[m].frequency, 0.0, 6.0E9); + double rx_freq; + cfg.GetValue(path+"rx_frequency", type, rx_freq, 0.0, 6.0E9); + if (0.0 == rx_freq) + rx_freq = rptr.mod[m].frequency; + rptr.mod[m].offset = rx_freq - rptr.mod[m].frequency; + } else if (cfg.KeyExists(path+"frequency")) { + cfg.GetValue(path+"frequency", type, rptr.mod[m].frequency, 0.0, 1.0E9); + rptr.mod[m].offset = 0.0; + } else { + rptr.mod[m].frequency = rptr.mod[m].offset = 0.0; + } + cfg.GetValue(path+"range", type, rptr.mod[m].range, 0.0, 1609344.0); + cfg.GetValue(path+"agl", type, rptr.mod[m].agl, 0.0, 1000.0); + // make the long description for the log if (rptr.mod[m].desc1.length()) rptr.mod[m].desc = rptr.mod[m].desc1 + ' '; rptr.mod[m].desc += rptr.mod[m].desc2; - } else - rptr.mod[m].defined = false; + } } - if (false==rptr.mod[0].defined && false==rptr.mod[1].defined && false==rptr.mod[2].defined) { - printf("No repeaters defined!\n"); + if (! (rptr.mod[0].defined || rptr.mod[1].defined || rptr.mod[2].defined)) { + printf("No modules defined!\n"); return true; } - if (! get_value(cfg, "file.status", status_file, 1, FILENAME_MAX, "/usr/local/etc/RPTR_STATUS.txt")) - return true; - - if (! get_value(cfg, "gateway.local_irc_ip", local_irc_ip, 7, IP_SIZE, "0.0.0.0")) - return true; - - get_value(cfg, "gateway.send_qrgs_maps", bool_send_qrgs, true); - - if (! get_value(cfg, "aprs.host", rptr.aprs.ip, 7, MAXHOSTNAMELEN, "rotate.aprs.net")) - return true; - - get_value(cfg, "aprs.port", rptr.aprs.port, 10000, 65535, 14580); - - get_value(cfg, "aprs.interval", rptr.aprs_interval, 40, 1000, 40); - - if (! get_value(cfg, "aprs.filter", rptr.aprs_filter, 0, 512, "")) - return true; - - if (! get_value(cfg, "gateway.external.ip", g2_external.ip, 7, IP_SIZE, "0.0.0.0")) - return true; - - get_value(cfg, "gateway.external.port", g2_external.port, 1024, 65535, 40000); - - if (! get_value(cfg, "gateway.internal.ip", g2_internal.ip, 7, IP_SIZE, "0.0.0.0")) - return true; - - get_value(cfg, "gateway.internal.port", g2_internal.port, 16000, 65535, 19000); - - if (! get_value(cfg, "link.outgoing_ip", g2_link.ip, 7, IP_SIZE, "127.0.0.1")) - return true; - - get_value(cfg, "link.port", g2_link.port, 16000, 65535, 18997); - - get_value(cfg, "log.qso", bool_qso_details, false); - - get_value(cfg, "log.irc", bool_irc_debug, false); - - get_value(cfg, "log.dtmf", bool_dtmf_debug, false); - - get_value(cfg, "gateway.regen_header", bool_regen_header, true); - - get_value(cfg, "gateway.aprs_send", bool_send_aprs, true); - - if (! get_value(cfg, "file.echotest", echotest_dir, 2, FILENAME_MAX, "/tmp")) - return true; - - get_value(cfg, "timing.play.wait", play_wait, 1, 10, 2); - - get_value(cfg, "timing.play.delay", play_delay, 9, 25, 19); - - get_value(cfg, "timing.timeeout.echo", echotest_rec_timeout, 1, 10, 1); - - get_value(cfg, "timing.timeout.voicemail", voicemail_rec_timeout, 1, 10, 1); - - get_value(cfg, "timing.timeout.remote_g2", from_remote_g2_timeout, 1, 10, 2); - - get_value(cfg, "timing.timeout.local_rptr", from_local_rptr_timeout, 1, 10, 1); - - if (! get_value(cfg, "ircddb.host", ircddb.ip, 3, MAXHOSTNAMELEN, "rr.openquad.net")) - return true; - - get_value(cfg, "ircddb.port", ircddb.port, 1000, 65535, 9007); - - if(! get_value(cfg, "ircddb.password", irc_pass, 0, 512, "1111111111111111")) - return true; - - if (! get_value(cfg, "file.dtmf", dtmf_dir, 2,FILENAME_MAX, "/tmp")) - return true; + // gateway + path.assign("gateway_"); + cfg.GetValue(path+"ip", estr, g2_external.ip, 7, 64); + cfg.GetValue(path+"port", estr, g2_external.port, 1024, 65535); + cfg.GetValue(path+"ipv6_ip", estr, g2_ipv6_external.ip, 7, 64); + cfg.GetValue(path+"ipv6_port", estr, g2_ipv6_external.port, 1024, 65535); + cfg.GetValue(path+"header_regen", estr, GATEWAY_HEADER_REGEN); + cfg.GetValue(path+"send_qrgs_maps", estr, GATEWAY_SEND_QRGS_MAP); + cfg.GetValue(path+"gate2link", estr, gate2link, 1, FILENAME_MAX); + cfg.GetValue(path+"link2gate", estr, link2gate, 1, FILENAME_MAX); + cfg.GetValue(path+"modem2gate", estr, modem2gate, 1, FILENAME_MAX); + for (int m=0; m<3; m++) { + if (rptr.mod[m].defined) { + cfg.GetValue(path+"gate2modem"+std::string(1, 'a'+m), estr, gate2modem[m], 1, FILENAME_MAX); + cfg.GetValue(path+"latitude", estr, rptr.mod[m].latitude, -90.0, 90.0); + cfg.GetValue(path+"longitude", estr, rptr.mod[m].longitude, -180.0, 180.0); + cfg.GetValue(path+"desc1", estr, rptr.mod[m].desc1, 0, 20); + cfg.GetValue(path+"desc2", estr, rptr.mod[m].desc2, 0, 20); + cfg.GetValue(path+"url", estr, rptr.mod[m].url, 0, 80); + } + } + path.append("find_route"); + if (cfg.KeyExists(path)) { + std::string csv; + cfg.GetValue(path, estr, csv, 0, 10240); + UnpackCallsigns(csv, findRoute); + PrintCallsigns(path, findRoute); + } + + // APRS + path.assign("aprs_"); + cfg.GetValue(path+"enable", estr, APRS_ENABLE); + cfg.GetValue(path+"host", estr, rptr.aprs.ip, 7, MAXHOSTNAMELEN); + cfg.GetValue(path+"port", estr, rptr.aprs.port, 10000, 65535); + cfg.GetValue(path+"interval", estr, rptr.aprs_interval, 40, 1000); + cfg.GetValue(path+"filter", estr, rptr.aprs_filter, 0, 512); + + // log + path.assign("log_"); + cfg.GetValue(path+"qso", estr, LOG_QSO); + cfg.GetValue(path+"irc", estr, LOG_IRC); + cfg.GetValue(path+"dtmf", estr, LOG_DTMF); + cfg.GetValue(path+"debug", estr, LOG_DEBUG); + + // file + path.assign("file_"); + cfg.GetValue(path+"echotest", estr, FILE_ECHOTEST, 2, FILENAME_MAX); + cfg.GetValue(path+"dtmf", estr, FILE_DTMF, 2, FILENAME_MAX); + cfg.GetValue(path+"status", estr, FILE_STATUS, 2, FILENAME_MAX); + cfg.GetValue(path+"qnvoice_file", estr, FILE_QNVOICE_FILE, 2, FILENAME_MAX); + + // timing + path.assign("timing_play_"); + cfg.GetValue(path+"wait", estr, TIMING_PLAY_WAIT, 1, 10); + cfg.GetValue(path+"delay", estr, TIMING_PLAY_DELAY, 9, 25); + path.assign("timing_timeout_"); + cfg.GetValue(path+"echo", estr, TIMING_TIMEOUT_ECHO, 1, 10); + cfg.GetValue(path+"voicemail", estr, TIMING_TIMEOUT_VOICEMAIL, 1, 10); + cfg.GetValue(path+"remote_g2", estr, TIMING_TIMEOUT_REMOTE_G2, 1, 10); + cfg.GetValue(path+"local_rptr", estr, TIMING_TIMEOUT_LOCAL_RPTR, 1, 10); return false; } // Create ports -int CQnetGateway::open_port(const SPORTIP &pip) +int CQnetGateway::open_port(const SPORTIP *pip, int family) { - struct sockaddr_in sin; + CSockAddress sin(family, pip->port, pip->ip.c_str()); - int sock = socket(PF_INET, SOCK_DGRAM, 0); + int sock = socket(family, SOCK_DGRAM, 0); if (0 > sock) { - printf("Failed to create socket on %s:%d, errno=%d, %s\n", pip.ip.c_str(), pip.port, errno, strerror(errno)); + printf("Failed to create socket on %s:%d, errno=%d, %s\n", pip->ip.c_str(), pip->port, errno, strerror(errno)); return -1; } fcntl(sock, F_SETFL, O_NONBLOCK); - memset(&sin, 0, sizeof(struct sockaddr_in)); - sin.sin_family = AF_INET; - sin.sin_port = htons(pip.port); - sin.sin_addr.s_addr = inet_addr(pip.ip.c_str()); - - int reuse = 1; - if (::setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { - printf("Cannot set the UDP socket (port %u) option, err: %d, %s\n", pip.port, errno, strerror(errno)); - return -1; - } + //int reuse = 1; + //if (::setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { + // printf("Cannot set the UDP socket (port %u) option, err: %d, %s\n", pip->port, errno, strerror(errno)); + // return -1; + //} - if (bind(sock, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) != 0) { - printf("Failed to bind %s:%d, errno=%d, %s\n", pip.ip.c_str(), pip.port, errno, strerror(errno)); + if (bind(sock, sin.GetPointer(), sizeof(struct sockaddr_storage)) != 0) { + printf("Failed to bind %s:%d, errno=%d, %s\n", pip->ip.c_str(), pip->port, errno, strerror(errno)); close(sock); return -1; } @@ -392,7 +381,7 @@ int CQnetGateway::open_port(const SPORTIP &pip) } /* receive data from the irc server and save it */ -void CQnetGateway::GetIRCDataThread() +void CQnetGateway::GetIRCDataThread(const int i) { std::string user, rptr, gateway, ipaddr; DSTAR_PROTOCOL proto; @@ -420,10 +409,47 @@ void CQnetGateway::GetIRCDataThread() } short threshold = 0; + bool not_announced[3]; + for (int i=0; i<3; i++) + not_announced[i] = this->rptr.mod[i].defined; // announce to all modules that are defined! + bool is_quadnet = (std::string::npos != ircddb[i].ip.find(".openquad.net")); + bool doFind = true; while (keep_running) { + int rc = ii[i]->getConnectionState(); + if (rc > 5 && rc < 8 && is_quadnet) { + char ch = '\0'; + if (not_announced[0]) + ch = 'A'; + else if (not_announced[1]) + ch = 'B'; + else if (not_announced[2]) + ch = 'C'; + if (ch) { + // we need to announce, but can we? + struct stat sbuf; + if (stat(FILE_QNVOICE_FILE.c_str(), &sbuf)) { + // yes, there is no FILE_QNVOICE_FILE, so create it + FILE *fp = fopen(FILE_QNVOICE_FILE.c_str(), "w"); + if (fp) { + fprintf(fp, "%c_connected2network.dat_WELCOME_TO_QUADNET", ch); + fclose(fp); + not_announced[ch - 'A'] = false; + } else + fprintf(stderr, "could not open %s\n", FILE_QNVOICE_FILE.c_str()); + } + } + if (doFind) { + printf("Finding Routes for...\n"); + for (auto it=findRoute.begin(); it!=findRoute.end(); it++) { + std::this_thread::sleep_for(std::chrono::milliseconds(800)); + printf("\t'%s'\n", it->c_str()); + ii[i]->findUser(*it); + } + doFind = false; + } + } threshold++; if (threshold >= 100) { - int rc = ii->getConnectionState(); if ((rc == 0) || (rc == 10)) { if (last_status != 0) { printf("irc status=%d, probable disconnect...\n", rc); @@ -443,1630 +469,1570 @@ void CQnetGateway::GetIRCDataThread() threshold = 0; } - while (((type = ii->getMessageType()) != IDRT_NONE) && keep_running) { - if (type == IDRT_USER) { - ii->receiveUser(user, rptr, gateway, ipaddr); + while (((type = ii[i]->getMessageType()) != IDRT_NONE) && keep_running) { + switch (type) { + case IDRT_USER: + ii[i]->receiveUser(user, rptr, gateway, ipaddr); if (!user.empty()) { if (!rptr.empty() && !gateway.empty() && !ipaddr.empty()) { - if (bool_irc_debug) + if (LOG_IRC) printf("C-u:%s,%s,%s,%s\n", user.c_str(), rptr.c_str(), gateway.c_str(), ipaddr.c_str()); - pthread_mutex_lock(&irc_data_mutex); + pthread_mutex_lock(&irc_data_mutex[i]); - user2rptr_map[user] = rptr; - rptr2gwy_map[rptr] = gateway; - gwy2ip_map[gateway] = ipaddr; + user2rptr_map[i][user] = rptr; + rptr2gwy_map[i][rptr] = gateway; + gwy2ip_map[i][gateway] = ipaddr; - pthread_mutex_unlock(&irc_data_mutex); + pthread_mutex_unlock(&irc_data_mutex[i]); // printf("%d users, %d repeaters, %d gateways\n", user2rptr_map.size(), rptr2gwy_map.size(), gwy2ip_map.size()); } } - } else if (type == IDRT_REPEATER) { - ii->receiveRepeater(rptr, gateway, ipaddr, proto); + break; + case IDRT_REPEATER: + ii[i]->receiveRepeater(rptr, gateway, ipaddr, proto); if (!rptr.empty()) { if (!gateway.empty() && !ipaddr.empty()) { - if (bool_irc_debug) + if (LOG_IRC) printf("C-r:%s,%s,%s\n", rptr.c_str(), gateway.c_str(), ipaddr.c_str()); - pthread_mutex_lock(&irc_data_mutex); + pthread_mutex_lock(&irc_data_mutex[i]); - rptr2gwy_map[rptr] = gateway; - gwy2ip_map[gateway] = ipaddr; + rptr2gwy_map[i][rptr] = gateway; + gwy2ip_map[i][gateway] = ipaddr; - pthread_mutex_unlock(&irc_data_mutex); + pthread_mutex_unlock(&irc_data_mutex[i]); // printf("%d repeaters, %d gateways\n", rptr2gwy_map.size(), gwy2ip_map.size()); } } - } else if (type == IDRT_GATEWAY) { - ii->receiveGateway(gateway, ipaddr, proto); + break; + case IDRT_GATEWAY: + ii[i]->receiveGateway(gateway, ipaddr, proto); if (!gateway.empty() && !ipaddr.empty()) { - if (bool_irc_debug) + if (LOG_IRC) printf("C-g:%s,%s\n", gateway.c_str(),ipaddr.c_str()); - pthread_mutex_lock(&irc_data_mutex); + pthread_mutex_lock(&irc_data_mutex[i]); - gwy2ip_map[gateway] = ipaddr; + gwy2ip_map[i][gateway] = ipaddr; - pthread_mutex_unlock(&irc_data_mutex); + pthread_mutex_unlock(&irc_data_mutex[i]); // printf("%d gateways\n", gwy2ip_map.size()); } - } - } + break; + case IDRT_PING: + ii[i]->receivePing(rptr); + ReplaceChar(rptr, '_', ' '); + if (! rptr.empty()) { + pthread_mutex_lock(&irc_data_mutex[i]); + auto git = rptr2gwy_map[i].find(rptr); + if (rptr2gwy_map[i].end() != git) { + gateway = git->second; + auto ait = gwy2ip_map[i].find(gateway); + if (gwy2ip_map[i].end() != ait) { + ipaddr = ait->second; + CSockAddress to(af_family[i], (unsigned short)((AF_INET==af_family[i])?g2_external.port:g2_ipv6_external.port), ipaddr.c_str()); + sendto(g2_sock[i], "PONG", 4, 0, to.GetPointer(), to.GetSize()); + if (LOG_QSO) + printf("Sent 'PONG' to %s\n", ipaddr.c_str()); + //} else { + // printf("Can't respond to PING, gateway %s not in gwy2ip_map\n", gateway.c_str()); + } + //} else { + // printf("Can't respond to PING, repeater %s not in rptr2gwy_map\n", rptr.c_str()); + } + pthread_mutex_unlock(&irc_data_mutex[i]); + } + break; + default: + break; + } // switch (type) + } // while (keep_running) std::this_thread::sleep_for(std::chrono::milliseconds(500)); } - printf("GetIRCDataThread exiting...\n"); + printf("GetIRCDataThread[%i] exiting...\n", i); return; } /* return codes: 0=OK(found it), 1=TRY AGAIN, 2=FAILED(bad data) */ -int CQnetGateway::get_yrcall_rptr_from_cache(char *call, char *arearp_cs, char *zonerp_cs, char *mod, char *ip, char RoU) +int CQnetGateway::get_yrcall_rptr_from_cache(const int i, const std::string &call, std::string &arearp_cs, std::string &zonerp_cs, char *mod, std::string &ip, char RoU) { - char temp[CALL_SIZE + 1]; + std::string temp; - memset(arearp_cs, ' ', CALL_SIZE); - arearp_cs[CALL_SIZE] = '\0'; - memset(zonerp_cs, ' ', CALL_SIZE); - zonerp_cs[CALL_SIZE] = '\0'; + arearp_cs.clear(); + zonerp_cs.clear(); *mod = ' '; /* find the user in the CACHE */ if (RoU == 'U') { - auto user_pos = user2rptr_map.find(call); - if (user_pos != user2rptr_map.end()) { - memcpy(arearp_cs, user_pos->second.c_str(), 7); - *mod = user_pos->second.c_str()[7]; - } else + auto user_pos = user2rptr_map[i].find(call); + if (user_pos != user2rptr_map[i].end()) { + arearp_cs = user_pos->second.substr(0, 7); + *mod = user_pos->second.at(7); + } else { + if (1==i || NULL==ii[1]) + printf("could not find a repeater for user %s\n", call.c_str()); return 1; + } } else if (RoU == 'R') { - memcpy(arearp_cs, call, 7); - *mod = call[7]; + arearp_cs = call.substr(0, 7); + *mod = call.at(7); } else { - printf("Invalid specification %c for RoU\n", RoU); + fprintf(stderr, "ERROR: Invalid specification %c for RoU\n", RoU); return 2; } - if ((*mod != 'A') && (*mod != 'B') && (*mod != 'C')) { - printf("Invalid module %c\n", *mod); + if (*mod == 'G') { + fprintf(stderr, "ERROR: Invalid module %c\n", *mod); return 2; } - memcpy(temp, arearp_cs, 7); - temp[7] = *mod; - temp[CALL_SIZE] = '\0'; + temp.assign(arearp_cs); + temp.append(1, *mod); + arearp_cs.resize(8, ' '); - auto rptr_pos = rptr2gwy_map.find(temp); - if (rptr_pos != rptr2gwy_map.end()) { - memcpy(zonerp_cs, rptr_pos->second.c_str(), CALL_SIZE); - zonerp_cs[CALL_SIZE] = '\0'; + auto rptr_pos = rptr2gwy_map[i].find(temp); + if (rptr_pos != rptr2gwy_map[i].end()) { + zonerp_cs.assign(rptr_pos->second); - auto gwy_pos = gwy2ip_map.find(zonerp_cs); - if (gwy_pos != gwy2ip_map.end()) { - strncpy(ip, gwy_pos->second.c_str(), IP_SIZE); - ip[IP_SIZE] = '\0'; + auto gwy_pos = gwy2ip_map[i].find(zonerp_cs); + if (gwy_pos != gwy2ip_map[i].end()) { + ip.assign(gwy_pos->second); return 0; } else { - /* printf("Could not find IP for Gateway %s\n", zonerp_cs); */ + printf("Could not find IP for Gateway %s\n", zonerp_cs.c_str()); return 1; } } else { - /* printf("Could not find Gateway for repeater %s\n", temp); */ + printf("Could not find Gateway for repeater %s\n", temp.c_str()); return 1; } - - return 2; } -bool CQnetGateway::get_yrcall_rptr(char *call, char *arearp_cs, char *zonerp_cs, char *mod, char *ip, char RoU) +int CQnetGateway::get_yrcall_rptr(const std::string &call, std::string &arearp_cs, std::string &zonerp_cs, char *mod, std::string &ip, char RoU) +// returns 0 if unsuccessful, otherwise returns ii index plus one { - pthread_mutex_lock(&irc_data_mutex); - int rc = get_yrcall_rptr_from_cache(call, arearp_cs, zonerp_cs, mod, ip, RoU); - pthread_mutex_unlock(&irc_data_mutex); - if (rc == 0) - return true; - else if (rc == 2) - return false; - - /* at this point, the data is not in cache */ - /* report the irc status */ - int status = ii->getConnectionState(); - // printf("irc status=%d\n", status); - if (status != 7) { - printf("Remote irc database not ready, irc status is not 7, try again\n"); - return false; + for (int i=0; i<2; i++) { + if (NULL == ii[i]) + continue; + int rc; + if (ii[i]) { + pthread_mutex_lock(&irc_data_mutex[i]); + rc = get_yrcall_rptr_from_cache(i, call, arearp_cs, zonerp_cs, mod, ip, RoU); + pthread_mutex_unlock(&irc_data_mutex[i]); + } + if (rc == 0) { + //printf("get_yrcall_rptr_from_cache: call='%s' arearp_cs='%s' zonerp_cs='%s', mod=%c ip='%s' RoU=%c\n", call.c_str(), arearp_cs.c_str(), zonerp_cs.c_str(), *mod, ip.c_str(), RoU); + return i+1; + } else if (rc == 2) + return 0; } - /* request data from irc server */ - if (RoU == 'U') { - printf("User [%s] not in local cache, try again\n", call); - /*** YRCALL=KJ4NHFBL ***/ - if (((call[6] == 'A') || (call[6] == 'B') || (call[6] == 'C')) && (call[7] == 'L')) - printf("If this was a gateway link request, that is ok\n"); - - if (!ii->findUser(call)) { - printf("findUser(%s): Network error\n", call); - return false; - } - } else if (RoU == 'R') { - printf("Repeater [%s] not in local cache, try again\n", call); - if (!ii->findRepeater(call)) { - printf("findRepeater(%s): Network error\n", call); - return false; + for (int i=0; i<2; i++) { + if (NULL == ii[i]) + continue; + /* at this point, the data is not in cache */ + /* report the irc status */ + int status = ii[i]->getConnectionState(); + // printf("irc status=%d\n", status); + if (7 == status) { + /* request data from irc server */ + if (RoU == 'U') { + printf("User [%s] not in local cache, try again\n", call.c_str()); + /*** YRCALL=KJ4NHFBL ***/ + if (((call.at(6) == 'A') || (call.at(6) == 'B') || (call.at(6) == 'C')) && (call.at(7) == 'L')) + printf("If this was a gateway link request, that is ok\n"); + + if (!ii[i]->findUser(call)) { + printf("findUser(%s): Network error\n", call.c_str()); + return 0; + } + } else if (RoU == 'R') { + printf("Repeater [%s] not in local cache, try again\n", call.c_str()); + if (!ii[i]->findRepeater(call)) { + printf("findRepeater(%s): Network error\n", call.c_str()); + return 0; + } + } } } - return false; + return 0; } -/* run the main loop for QnetGateway */ -void CQnetGateway::process() +bool CQnetGateway::Flag_is_ok(unsigned char flag) { - SDSVT g2buf; - fd_set fdset; - - char temp_radio_user[CALL_SIZE + 1]; - char temp_mod; - time_t t_now; - - char arearp_cs[CALL_SIZE + 1]; - char zonerp_cs[CALL_SIZE + 1]; - char ip[IP_SIZE + 1]; - - char tempfile[FILENAME_MAX + 1]; - long num_recs = 0L; - short int rec_len = 56; - - std::future aprs_future, irc_data_future; - - // dtmf stuff - int dtmf_buf_count[3] = {0, 0, 0}; - char dtmf_buf[3][MAX_DTMF_BUF + 1] = { {""}, {""}, {""} }; - int dtmf_last_frame[3] = { 0, 0, 0 }; - unsigned int dtmf_counter[3] = { 0, 0, 0 }; - - /* START: TEXT crap */ - bool new_group[3] = { true, true, true }; - int header_type = 0; - short to_print[3] = { 0, 0, 0 }; - bool ABC_grp[3] = { false, false, false }; - bool C_seen[3] = { false, false, false }; - unsigned char tmp_txt[3]; - /* END: TEXT crap */ - - int max_nfds = 0; - - dstar_dv_init(); - - if (g2_sock > max_nfds) - max_nfds = g2_sock; - if (srv_sock > max_nfds) - max_nfds = srv_sock; - printf("g2=%d, srv=%d, MAX+1=%d\n", g2_sock, srv_sock, max_nfds + 1); - - /* start the beacon thread */ - if (bool_send_aprs) { - try { - aprs_future = std::async(std::launch::async, &CQnetGateway::APRSBeaconThread, this); - } catch (const std::exception &e) { - printf("Failed to start the APRSBeaconThread. Exception: %s\n", e.what()); - } - if (aprs_future.valid()) - printf("APRS beacon thread started\n"); - } - - try { - irc_data_future = std::async(std::launch::async, &CQnetGateway::GetIRCDataThread, this); - } catch (const std::exception &e) { - printf("Failed to start GetIRCDataThread. Exception: %s\n", e.what()); - keep_running = false; - } - if (keep_running) - printf("get_irc_data thread started\n"); - - ii->kickWatchdog(IRCDDB_VERSION); + // normal break emr emr+break + return 0x00U==flag || 0x08U==flag || 0x20U==flag || 0x28U==flag; +} - while (keep_running) { - for (int i=0; i<3; i++) { - /* echotest recording timed out? */ - if (recd[i].last_time != 0) { - time(&t_now); - if ((t_now - recd[i].last_time) > echotest_rec_timeout) { - printf("Inactivity on echotest recording mod %d, removing stream id=%04x\n", - i, recd[i].streamid); - - recd[i].streamid = 0; - recd[i].last_time = 0; - close(recd[i].fd); - recd[i].fd = -1; - // printf("Closed echotest audio file:[%s]\n", recd[i].file); - - /* START: echotest thread setup */ - try { - std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, recd[i].file); - } catch (const std::exception &e) { - printf("Failed to start echotest thread. Exception: %s\n", e.what()); - // when the echotest thread runs, it deletes the file, - // Because the echotest thread did NOT start, we delete the file here - unlink(recd[i].file); - } - /* END: echotest thread setup */ +void CQnetGateway::ProcessTimeouts() +{ + for (int i=0; i<3; i++) { + time_t t_now; + /* echotest recording timed out? */ + if (recd[i].last_time != 0) { + time(&t_now); + if ((t_now - recd[i].last_time) > TIMING_TIMEOUT_ECHO) { + printf("Inactivity on echotest recording module %c, removing stream id=%04x\n", 'A'+i, ntohs(recd[i].streamid)); + + recd[i].streamid = 0; + recd[i].last_time = 0; + close(recd[i].fd); + recd[i].fd = -1; + // printf("Closed echotest audio file:[%s]\n", recd[i].file); + + /* START: echotest thread setup */ + try { + std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, std::ref(recd[i])); + } catch (const std::exception &e) { + printf("Failed to start echotest thread. Exception: %s\n", e.what()); + // when the echotest thread runs, it deletes the file, + // Because the echotest thread did NOT start, we delete the file here + unlink(recd[i].file); } + /* END: echotest thread setup */ } + } - /* voicemail recording timed out? */ - if (vm[i].last_time != 0) { - time(&t_now); - if ((t_now - vm[i].last_time) > voicemail_rec_timeout) { - printf("Inactivity on voicemail recording mod %d, removing stream id=%04x\n", - i, vm[i].streamid); - - vm[i].streamid = 0; - vm[i].last_time = 0; - close(vm[i].fd); - vm[i].fd = -1; - // printf("Closed voicemail audio file:[%s]\n", vm[i].file); - } + /* voicemail recording timed out? */ + if (vm[i].last_time != 0) { + time(&t_now); + if ((t_now - vm[i].last_time) > TIMING_TIMEOUT_VOICEMAIL) { + printf("Inactivity on voicemail recording module %c, removing stream id=%04x\n", 'A'+i, ntohs(vm[i].streamid)); + + vm[i].streamid = 0; + vm[i].last_time = 0; + close(vm[i].fd); + vm[i].fd = -1; + // printf("Closed voicemail audio file:[%s]\n", vm[i].file); } + } - // any stream going to local repeater timed out? - if (toRptr[i].last_time != 0) { - time(&t_now); - // The stream can be from a cross-band, or from a remote system, - // so we could use either FROM_LOCAL_RPTR_TIMEOUT or FROM_REMOTE_G2_TIMEOUT - // but FROM_REMOTE_G2_TIMEOUT makes more sense, probably is a bigger number - if ((t_now - toRptr[i].last_time) > from_remote_g2_timeout) { - printf("Inactivity to local rptr mod index %d, removing stream id %04x\n", i, toRptr[i].streamid); - - // Send end_of_audio to local repeater. - // Let the repeater re-initialize - end_of_audio.counter = toRptr[i].G2_COUNTER; - if (i == 0) - end_of_audio.vpkt.snd_term_id = 0x03; - else if (i == 1) - end_of_audio.vpkt.snd_term_id = 0x01; - else - end_of_audio.vpkt.snd_term_id = 0x02; - end_of_audio.vpkt.streamid = toRptr[i].streamid; - end_of_audio.vpkt.ctrl = toRptr[i].sequence | 0x40; + // any stream going to local repeater timed out? + if (toRptr[i].last_time != 0) { + time(&t_now); + // The stream can be from a cross-band, or from a remote system, + // so we could use either FROM_LOCAL_RPTR_TIMEOUT or FROM_REMOTE_G2_TIMEOUT + // but FROM_REMOTE_G2_TIMEOUT makes more sense, probably is a bigger number + if ((t_now - toRptr[i].last_time) > TIMING_TIMEOUT_REMOTE_G2) { + printf("Inactivity to local rptr module %c, removing stream id %04x\n", 'A'+i, ntohs(toRptr[i].streamid)); - for (int j=0; j<2; j++) - sendto(srv_sock, end_of_audio.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + // Send end_of_audio to local repeater. + // Let the repeater re-initialize + end_of_audio.streamid = toRptr[i].streamid; + end_of_audio.ctrl = toRptr[i].sequence | 0x40; - toRptr[i].G2_COUNTER++; + for (int j=0; j<2; j++) + Gate2Modem[i].Write(end_of_audio.title, 27); - toRptr[i].streamid = 0; - toRptr[i].adr = 0; - toRptr[i].last_time = 0; - } + + toRptr[i].streamid = 0; + toRptr[i].addr.ClearAddress(); + toRptr[i].last_time = 0; } + } - /* any stream coming from local repeater timed out ? */ - if (band_txt[i].last_time != 0) { - time(&t_now); - if ((t_now - band_txt[i].last_time) > from_local_rptr_timeout) { - /* This local stream never went to a remote system, so trace the timeout */ - if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) - printf("Inactivity from local rptr band %d, removing stream id %04x\n", i, band_txt[i].streamID); + /* any stream coming from local repeater timed out ? */ + if (band_txt[i].last_time != 0) { + time(&t_now); + if ((t_now - band_txt[i].last_time) > TIMING_TIMEOUT_LOCAL_RPTR) { + /* This local stream never went to a remote system, so trace the timeout */ + if (to_remote_g2[i].toDstar.AddressIsZero()) + printf("Inactivity from local rptr module %c, removing stream id %04x\n", 'A'+i, ntohs(band_txt[i].streamID)); - band_txt[i].streamID = 0; - band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0x0; - band_txt[i].lh_mycall[0] = '\0'; - band_txt[i].lh_sfx[0] = '\0'; - band_txt[i].lh_yrcall[0] = '\0'; - band_txt[i].lh_rpt1[0] = '\0'; - band_txt[i].lh_rpt2[0] = '\0'; + band_txt[i].streamID = 0; + band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0x0; + band_txt[i].lh_mycall[0] = '\0'; + band_txt[i].lh_sfx[0] = '\0'; + band_txt[i].lh_yrcall[0] = '\0'; + band_txt[i].lh_rpt1[0] = '\0'; + band_txt[i].lh_rpt2[0] = '\0'; - band_txt[i].last_time = 0; + band_txt[i].last_time = 0; - band_txt[i].txt[0] = '\0'; - band_txt[i].txt_cnt = 0; + band_txt[i].txt[0] = '\0'; + band_txt[i].txt_cnt = 0; - band_txt[i].dest_rptr[0] = '\0'; + band_txt[i].dest_rptr[0] = '\0'; - band_txt[i].num_dv_frames = 0; - band_txt[i].num_dv_silent_frames = 0; - band_txt[i].num_bit_errors = 0; - } + band_txt[i].num_dv_frames = 0; + band_txt[i].num_dv_silent_frames = 0; + band_txt[i].num_bit_errors = 0; } + } - /* any stream from local repeater to a remote gateway timed out ? */ - if (to_remote_g2[i].toDst4.sin_addr.s_addr != 0) { - time(&t_now); - if ((t_now - to_remote_g2[i].last_time) > from_local_rptr_timeout) { - printf("Inactivity from local rptr mod %d, removing stream id %04x\n", - i, to_remote_g2[i].streamid); + /* any stream from local repeater to a remote gateway timed out ? */ + if (! to_remote_g2[i].toDstar.AddressIsZero()) { + time(&t_now); + if ((t_now - to_remote_g2[i].last_time) > TIMING_TIMEOUT_LOCAL_RPTR) { + printf("Inactivity from local rptr mod %c, removing stream id %04x\n", 'A'+i, ntohs(to_remote_g2[i].streamid)); - memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].streamid = 0; - to_remote_g2[i].last_time = 0; - } + to_remote_g2[i].toDstar.Initialize(AF_UNSPEC); + to_remote_g2[i].streamid = 0; + to_remote_g2[i].last_time = 0; } } + } +} - /* wait 20 ms max */ - FD_ZERO(&fdset); - FD_SET(g2_sock, &fdset); - FD_SET(srv_sock, &fdset); - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 20000; /* 20 ms */ - (void)select(max_nfds + 1, &fdset, 0, 0, &tv); - - /* process packets coming from remote G2 */ - if (FD_ISSET(g2_sock, &fdset)) { - socklen_t fromlen = sizeof(struct sockaddr_in); - int g2buflen = recvfrom(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&fromDst4, &fromlen); - - // save incoming port for mobile systems - if (portmap.end() == portmap.find(fromDst4.sin_addr.s_addr)) { - printf("New g2 contact at %s on port %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); - portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); - } else { - if (ntohs(fromDst4.sin_port) != portmap[fromDst4.sin_addr.s_addr]) { - printf("New g2 port from %s is now %u, it was %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port), portmap[fromDst4.sin_addr.s_addr]); - portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); - } - } +// new_group is true if we are processing the first voice packet of a 2-voice packet pair. The high order nibble of the first byte of +// this first packet specifed the type of slow data that is being sent. +// the to_print is an integer that counts down how many 2-voice-frame pairs remain to be processed. +// ABC_grp means that we are processing a 20-character message. +// C_seen means that we are processing the last 2-voice-frame packet on a 20 character message. +void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid) +{ + /* extract 20-byte RADIO ID */ + if ((data[0] != 0x55) || (data[1] != 0x2d) || (data[2] != 0x16)) { + // first, unscramble + unsigned char c1 = data[0] ^ 0x70u; + unsigned char c2 = data[1] ^ 0x4fu; + unsigned char c3 = data[2] ^ 0x93u; - if ( ((g2buflen == 56) || (g2buflen == 27)) && - (0==memcmp(g2buf.title, "DSVT", 4)) && - ((g2buf.config == 0x10) || (g2buf.config == 0x20)) && /* header or voiceframe */ - (g2buf.id == 0x20)) { /* voice type */ - if (g2buflen == 56) { + for (int i=0; i<3; i++) { + if (band_txt[i].streamID == sid) { + if (new_group[i]) { + header_type = c1 & 0xf0; + + // header squelch + if ((header_type == 0x50) || (header_type == 0xc0)) { + new_group[i] = false; + to_print[i] = 0; + ABC_grp[i] = false; + } + else if (header_type == 0x30) { /* GPS or GPS id or APRS */ + new_group[i] = false; + to_print[i] = c1 & 0x0f; + ABC_grp[i] = false; + if (to_print[i] > 5) + to_print[i] = 5; + else if (to_print[i] < 1) + to_print[i] = 1; + + if ((to_print[i] > 1) && (to_print[i] <= 5)) { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } - // Find out the local repeater module IP/port to send the data to - int i = g2buf.hdr.rpt1[7] - 'A'; + /* fresh GPS string, re-initialize */ + if ((to_print[i] == 5) && (c2 == '$')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } - /* valid repeater module? */ - if (i>=0 && i<3) { - // toRptr[i] is active if a remote system is talking to it or - // toRptr[i] is receiving data from a cross-band - if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0) && - ((g2buf.hdr.flag[0] == 0x00) || - (g2buf.hdr.flag[0] == 0x01) || /* allow the announcements from g2_link */ - (g2buf.hdr.flag[0] == 0x08) || - (g2buf.hdr.flag[0] == 0x20) || - (g2buf.hdr.flag[0] == 0x28) || - (g2buf.hdr.flag[0] == 0x40))) { - if (bool_qso_details) - printf("START from g2: streamID=%04x, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s and port %u\n", - g2buf.streamid, - g2buf.hdr.flag[0], g2buf.hdr.flag[1], g2buf.hdr.flag[2], - g2buf.hdr.mycall, - g2buf.hdr.sfx, g2buf.hdr.urcall, - g2buf.hdr.rpt1, g2buf.hdr.rpt2, - g2buflen, inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); - - memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = toRptr[i].G2_COUNTER; - rptrbuf.flag[0] = 0x73; - rptrbuf.flag[1] = 0x12; - rptrbuf.flag[2] = 0x00; - rptrbuf.remaining = 0x30; - rptrbuf.vpkt.icm_id = 0x20; - memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 47); - sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* save the header */ - memcpy(toRptr[i].saved_hdr, rptrbuf.pkt_id, 58); - toRptr[i].saved_adr = fromDst4.sin_addr.s_addr; + /* do not copy CR, NL */ + if ((c2 != '\r') && (c2 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c2; + band_txt[i].temp_line_cnt++; + } + if ((c3 != '\r') && (c3 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c3; + band_txt[i].temp_line_cnt++; + } - /* This is the active streamid */ - toRptr[i].streamid = g2buf.streamid; - toRptr[i].adr = fromDst4.sin_addr.s_addr; + if ((c2 == '\r') || (c3 == '\r')) { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (APRS_ENABLE && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if ((c2 == '\n') || (c3 == '\n')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + to_print[i] -= 2; + } else { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } - /* time it, in case stream times out */ - time(&toRptr[i].last_time); + /* do not copy CR, NL */ + if ((c2 != '\r') && (c2 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c2; + band_txt[i].temp_line_cnt++; + } - /* bump the G2 counter */ - toRptr[i].G2_COUNTER++; + if (c2 == '\r') { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (APRS_ENABLE && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if (c2 == '\n') { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + to_print[i] --; + } + } + else if (header_type == 0x40) { /* ABC text */ + new_group[i] = false; + to_print[i] = 3; + ABC_grp[i] = true; + C_seen[i] = ((c1 & 0x0f) == 0x03) ? true : false; + + band_txt[i].txt[band_txt[i].txt_cnt] = c2; + band_txt[i].txt_cnt++; + + band_txt[i].txt[band_txt[i].txt_cnt] = c3; + band_txt[i].txt_cnt++; + + /* We should NOT see any more text, + if we already processed text, + so blank out the codes. */ + if (band_txt[i].sent_key_on_msg) { + data[0] = 0x70; + data[1] = 0x4f; + data[2] = 0x93; + } - toRptr[i].sequence = rptrbuf.vpkt.ctrl; + if (band_txt[i].txt_cnt >= 20) { + band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; + band_txt[i].txt_cnt = 0; } } - } else { - if (g2buf.counter & 0x40) { - if (bool_qso_details) - printf("END from g2: streamID=%04x, %d bytes from IP=%s\n", g2buf.streamid, g2buflen,inet_ntoa(fromDst4.sin_addr)); + else { // header type is not header, squelch, gps or message + new_group[i] = false; + to_print[i] = 0; + ABC_grp[i] = false; } + } + else { // not a new_group, this is the second of a two-voice-frame pair + if (! band_txt[i].sent_key_on_msg && vPacketCount[i] > 100) { + // 100 voice packets received and still no 20-char message! + /*** if YRCALL is CQCQCQ, set dest_rptr ***/ + band_txt[i].txt[0] = '\0'; + if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { + set_dest_rptr(i, band_txt[i].dest_rptr); + if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) + band_txt[i].dest_rptr[0] = '\0'; + } - /* find out which repeater module to send the data to */ - int i; - for (i=0; i<3; i++) { - /* streamid match ? */ - if ((toRptr[i].streamid==g2buf.streamid) && - (toRptr[i].adr == fromDst4.sin_addr.s_addr)) { - memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = toRptr[i].G2_COUNTER; - rptrbuf.flag[0] = 0x73; - rptrbuf.flag[1] = 0x12; - rptrbuf.flag[2] = 0x00; - rptrbuf.remaining= 0x13; - rptrbuf.vpkt.icm_id = 0x20; - memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 18); - - sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* timeit */ - time(&toRptr[i].last_time); + int x = FindIndex(i); + if (x >= 0) + ii[x]->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx, (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); + band_txt[i].sent_key_on_msg = true; + } + if (to_print[i] == 3) { + if (ABC_grp[i]) { + band_txt[i].txt[band_txt[i].txt_cnt] = c1; + band_txt[i].txt_cnt++; + + band_txt[i].txt[band_txt[i].txt_cnt] = c2; + band_txt[i].txt_cnt++; + + band_txt[i].txt[band_txt[i].txt_cnt] = c3; + band_txt[i].txt_cnt++; + + /* We should NOT see any more text, + if we already processed text, + so blank out the codes. */ + if (band_txt[i].sent_key_on_msg) { + data[0] = 0x70; + data[1] = 0x4f; + data[2] = 0x93; + } - /* bump G2 counter */ - toRptr[i].G2_COUNTER++; + if ((band_txt[i].txt_cnt >= 20) || C_seen[i]) { + band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; + if ( ! band_txt[i].sent_key_on_msg) { + /*** if YRCALL is CQCQCQ, set dest_rptr ***/ + if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { + set_dest_rptr(i, band_txt[i].dest_rptr); + if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) + band_txt[i].dest_rptr[0] = '\0'; + } + // we have the 20-character message, send it to the server... + int x = FindIndex(i); + if (x >= 0) + ii[x]->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx, (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); + band_txt[i].sent_key_on_msg = true; + } + band_txt[i].txt_cnt = 0; + } + if (C_seen[i]) + C_seen[i] = false; + } else { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } - toRptr[i].sequence = rptrbuf.vpkt.ctrl; + /* do not copy carrige return or newline */ + if ((c1 != '\r') && (c1 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c1; + band_txt[i].temp_line_cnt++; + } + if ((c2 != '\r') && (c2 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c2; + band_txt[i].temp_line_cnt++; + } + if ((c3 != '\r') && (c3 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c3; + band_txt[i].temp_line_cnt++; + } - /* End of stream ? */ - if (g2buf.counter & 0x40) { - /* clear the saved header */ - memset(toRptr[i].saved_hdr, 0, sizeof(toRptr[i].saved_hdr)); - toRptr[i].saved_adr = 0; + if ( (c1 == '\r') || (c2 == '\r') || (c3 == '\r') ) { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (APRS_ENABLE && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + else if ((c1 == '\n') || (c2 == '\n') ||(c3 == '\n')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + } + } else if (to_print[i] == 2) { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + + /* do not copy CR, NL */ + if ((c1 != '\r') && (c1 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c1; + band_txt[i].temp_line_cnt++; + } + if ((c2 != '\r') && (c2 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c2; + band_txt[i].temp_line_cnt++; + } - toRptr[i].last_time = 0; - toRptr[i].streamid = 0; - toRptr[i].adr = 0; + if ((c1 == '\r') || (c2 == '\r')) { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (APRS_ENABLE && !band_txt[i].is_gps_sent) + gps_send(i); } - break; + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if ((c1 == '\n') || (c2 == '\n')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + } else if (to_print[i] == 1) { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; } - } - /* no match ? */ - if ((i == 3) && bool_regen_header) { - /* check if this a continuation of audio that timed out */ + /* do not copy CR, NL */ + if ((c1 != '\r') && (c1 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c1; + band_txt[i].temp_line_cnt++; + } - if (g2buf.counter & 0x40) - ; /* we do not care about end-of-QSO */ - else { - /* for which repeater this stream has timed out ? */ - for (i = 0; i < 3; i++) { - /* match saved stream ? */ - if (0==memcmp(toRptr[i].saved_hdr + 14, &g2buf.streamid, 2) && toRptr[i].saved_adr==fromDst4.sin_addr.s_addr) { - /* repeater module is inactive ? */ - if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { - printf("Re-generating header for streamID=%04x\n", g2buf.streamid); + if (c1 == '\r') { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (APRS_ENABLE && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if (c1 == '\n') { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + } + new_group[i] = true; + to_print[i] = 0; + ABC_grp[i] = false; + } + break; + } + } + } +} - toRptr[i].saved_hdr[5] = (unsigned char)(toRptr[i].G2_COUNTER & 0xff); - toRptr[i].saved_hdr[4] = (unsigned char)((toRptr[i].G2_COUNTER >> 8) & 0xff); +void CQnetGateway::ProcessG2(const ssize_t g2buflen, const SDSVT &g2buf, const int source_sock) +// source_sock is the socket number of the incoming data, or -1 if it's a unix socket +{ + static unsigned char lastctrl = 20U; + static std::string superframe[3]; + if ( (g2buflen==56 || g2buflen==27) && 0==memcmp(g2buf.title, "DSVT", 4) && (g2buf.config==0x10 || g2buf.config==0x20) && g2buf.id==0x20) { + if (g2buflen == 56) { + // Find out the local repeater module IP/port to send the data to + int i = g2buf.hdr.rpt1[7] - 'A'; + /* valid repeater module? */ + if (i>=0 && i<3 && rptr.mod[i].defined) { + // toRptr[i] is active if a remote system is talking to it or + // toRptr[i] is receiving data from a cross-band + if (0==toRptr[i].last_time && 0==band_txt[i].last_time && (Flag_is_ok(g2buf.hdr.flag[0]) || 0x01U==g2buf.hdr.flag[0] || 0x40U==g2buf.hdr.flag[0])) { + superframe[i].clear(); + if (LOG_QSO) { + printf("id=%04x flags=%02x:%02x:%02x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s ", ntohs(g2buf.streamid), g2buf.hdr.flag[0], g2buf.hdr.flag[1], g2buf.hdr.flag[2], g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.mycall, g2buf.hdr.sfx); + if (source_sock >= 0) + printf("IP=[%s]:%u\n", fromDstar.GetAddress(), fromDstar.GetPort()); + else + printf("UnixSock=%s\n", link2gate.c_str()); + } - /* re-generate/send the header */ - sendto(srv_sock, toRptr[i].saved_hdr, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + Gate2Modem[i].Write(g2buf.title, 56); + lastctrl = 20U; + + /* save the header */ + if (source_sock < 0) { + //if (AF_INET == af_family) { + char address[16]; + snprintf(address, 16, "%d.0.0.0", i); + fromDstar.Initialize(AF_INET, 40000U, address); + //} else { + // char address[8]; + // snprintf(address, 8, "%d::", i); + // fromDstar.Initialize(AF_INET6, 40000U, address); + //} + } + memcpy(toRptr[i].saved_hdr.title, g2buf.title, 56); + toRptr[i].saved_addr = fromDstar; + + /* This is the active streamid */ + toRptr[i].streamid = g2buf.streamid; + toRptr[i].addr = fromDstar;; - /* bump G2 counter */ - toRptr[i].G2_COUNTER++; + /* time it, in case stream times out */ + time(&toRptr[i].last_time); - /* send this audio packet to repeater */ - memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = toRptr[i].G2_COUNTER; - rptrbuf.flag[0] = 0x73; - rptrbuf.flag[1] = 0x12; - rptrbuf.flag[2] = 0x00; - rptrbuf.remaining = 0x13; - rptrbuf.vpkt.icm_id = 0x20; - memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 18); + toRptr[i].sequence = g2buf.ctrl; + } + } + } else { // g2buflen == 27 + /* find out which repeater module to send the data to */ + int i; + for (i=0; i<3; i++) { + if (rptr.mod[i].defined) { + /* streamid match ? */ + bool match = (toRptr[i].streamid == g2buf.streamid); + if (source_sock >= 0) + match = match && (toRptr[i].addr == fromDstar); + if (match) { + if (LOG_DEBUG) { + const unsigned int ctrl = g2buf.ctrl & 0x3FU; + if (VoicePacketIsSync(g2buf.vasd.text)) { + if (superframe[i].size() > 65U) { + printf("Frame[%c]: %s\n", 'A'+i, superframe[i].c_str()); + superframe[i].clear(); + } + const char *ch = "#abcdefghijklmnopqrstuvwxyz"; + superframe[i].append(1, (ctrl<27U) ? ch[ctrl] : '%' ); + } else { + const char *ch = "!ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + superframe[i].append(1, (ctrl<27U) ? ch[ctrl] : '*' ); + } + } - sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + int diff = int(0x3FU & g2buf.ctrl) - int(lastctrl); + if (diff < 0) + diff += 21; + if (diff > 1 && diff < 6) { // fill up to 5 missing voice frames + if (LOG_DEBUG) + fprintf(stderr, "Warning: inserting %d missing voice frame(s)\n", diff - 1); + SDSVT dsvt; + memcpy(dsvt.title, g2buf.title, 14U); // everything but the ctrl and voice data + while (--diff > 0) { + lastctrl = (lastctrl + 1U) % 21U; + dsvt.ctrl = lastctrl; + if (dsvt.ctrl) { + const unsigned char silence[12] = { 0x9EU,0x8DU,0x32U,0x88U,0x26U,0x1AU,0x3FU,0x61U,0xE8U,0x70U,0x4FU,0x93U }; + memcpy(dsvt.vasd.voice, silence, 12U); + } else { + const unsigned char sync[12] = { 0x9EU,0x8DU,0x32U,0x88U,0x26U,0x1AU,0x3FU,0x61U,0xE8U,0x55U,0x2DU,0x16U }; + memcpy(dsvt.vasd.voice, sync, 12U); + } + Gate2Modem[i].Write(dsvt.title, 27); + } + } - /* make sure that any more audio arriving will be accepted */ - toRptr[i].streamid = g2buf.streamid; - toRptr[i].adr = fromDst4.sin_addr.s_addr; + if (((lastctrl + 1U) % 21U == (0x3FU & g2buf.ctrl)) || (0x40U & g2buf.ctrl)) { + // no matter what, we will send this on if it is the closing frame + lastctrl = (0x3FU & g2buf.ctrl); + Gate2Modem[i].Write(g2buf.title, 27); + } else { + if (LOG_DEBUG) + fprintf(stderr, "Warning: Ignoring packet because its ctrl=0x%02xU and lastctrl=0x%02xU\n", g2buf.ctrl, lastctrl); + } - /* time it, in case stream times out */ - time(&toRptr[i].last_time); + /* timeit */ + time(&toRptr[i].last_time); - /* bump the G2 counter */ - toRptr[i].G2_COUNTER++; + toRptr[i].sequence = g2buf.ctrl; - toRptr[i].sequence = rptrbuf.vpkt.ctrl; + /* End of stream ? */ + if (g2buf.ctrl & 0x40U) { + /* clear the saved header */ + memset(toRptr[i].saved_hdr.title, 0U, 56U); + toRptr[i].saved_addr.ClearAddress(); - } - break; - } + toRptr[i].last_time = 0; + toRptr[i].streamid = 0; + toRptr[i].addr.ClearAddress(); + if (LOG_DEBUG && superframe[i].size()) { + printf("Final[%c]: %s\n", 'A'+i, superframe[i].c_str()); + superframe[i].clear(); } + if (LOG_QSO) + printf("id=%04x END\n", ntohs(g2buf.streamid)); } + break; // we're done } } } - FD_CLR (g2_sock,&fdset); - } - /* process data coming from local repeater modules */ - if (FD_ISSET(srv_sock, &fdset)) { - socklen_t fromlen = sizeof(struct sockaddr_in); - int recvlen = recvfrom(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&fromRptr, &fromlen); + /* no match ? */ + if ((i == 3) && GATEWAY_HEADER_REGEN) { + /* check if this a continuation of audio that timed out */ - /* DV */ - if ( ((recvlen == 58) || (recvlen == 29) || (recvlen == 32)) && - (rptrbuf.flag[0] == 0x73) && (rptrbuf.flag[1] == 0x12) && - (0 == memcmp(rptrbuf.pkt_id,"DSTR", 4)) && - (rptrbuf.vpkt.icm_id == 0x20) && (rptrbuf.flag[2] == 0x00) && - ((rptrbuf.remaining == 0x30) || /* 48 bytes follow */ - (rptrbuf.remaining == 0x13) || /* 19 bytes follow */ - (rptrbuf.remaining == 0x16)) ) { /* 22 bytes follow */ + if (g2buf.ctrl & 0x40) + ; /* we do not care about end-of-QSO */ + else { + /* for which repeater this stream has timed out ? */ + for (i=0; i<3; i++) { + if (! rptr.mod[i].defined) + continue; + /* match saved stream ? */ + bool match = (toRptr[i].saved_hdr.streamid == g2buf.streamid); + if (source_sock >= 0) + match = match && (toRptr[i].saved_addr == fromDstar); + if (match) { + /* repeater module is inactive ? */ + if (toRptr[i].last_time==0 && band_txt[i].last_time==0) { + printf("Re-generating header for streamID=%04x\n", ntohs(g2buf.streamid)); + + /* re-generate/send the header */ + Gate2Modem[i].Write(toRptr[i].saved_hdr.title, 56); + + /* send this audio packet to repeater */ + Gate2Modem[i].Write(g2buf.title, 27); + + /* make sure that any more audio arriving will be accepted */ + toRptr[i].streamid = g2buf.streamid; + toRptr[i].addr = fromDstar; - if (recvlen == 58) { + /* time it, in case stream times out */ + time(&toRptr[i].last_time); - if (bool_qso_details) - printf("START from rptr: cntr=%04x, streamID=%04x, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s\n", - rptrbuf.counter, - rptrbuf.vpkt.streamid, - rptrbuf.vpkt.hdr.flag[0], rptrbuf.vpkt.hdr.flag[1], rptrbuf.vpkt.hdr.flag[2], - rptrbuf.vpkt.hdr.my, rptrbuf.vpkt.hdr.nm, rptrbuf.vpkt.hdr.ur, - rptrbuf.vpkt.hdr.r1, rptrbuf.vpkt.hdr.r2, recvlen, inet_ntoa(fromRptr.sin_addr)); + toRptr[i].sequence = g2buf.ctrl; - if ((memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) == 0) && /* rpt1 is this repeater */ - /*** (memcmp(rptrbuf + 44, OWNER, 7) != 0) && ***/ /* MYCALL is NOT this repeater */ - ((rptrbuf.vpkt.hdr.flag[0] == 0x00) || /* normal */ - (rptrbuf.vpkt.hdr.flag[0] == 0x08) || /* EMR */ - (rptrbuf.vpkt.hdr.flag[0] == 0x20) || /* BREAK */ - (rptrbuf.vpkt.hdr.flag[0] == 0x28))) { /* EMR + BREAK */ + } + break; + } + } + } + } + } + } +} - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; +void CQnetGateway::ProcessModem() +{ + char temp_mod; + char temp_radio_user[9]; + char tempfile[FILENAME_MAX]; + std::string arearp_cs, ip, zonerp_cs; + + SDSVT dsvt; + + int recvlen = Modem2Gate.Read(dsvt.title, 56); + + if (0 == memcmp(dsvt.title, "DSVT", 4)) { + if ( (recvlen==56 || recvlen==27) && dsvt.id==0x20U && (dsvt.config==0x10U || dsvt.config==0x20U) ) { + if (recvlen == 56) { + if (LOG_QSO) + printf("id=%04x start RPTR flag0=%02x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", ntohs(dsvt.streamid), dsvt.hdr.flag[0], dsvt.hdr.urcall, dsvt.hdr.rpt1, dsvt.hdr.rpt2, dsvt.hdr.mycall, dsvt.hdr.sfx); + if (0==memcmp(dsvt.hdr.rpt1, "DIRECT ", 8) && 0==memcmp(dsvt.hdr.rpt2, "DIRECT ", 8)) { // DIRECT mode??? + memcpy(dsvt.hdr.rpt1, OWNER.c_str(), 7); + switch (dsvt.flagb[2]) { + case 0x01U: + dsvt.hdr.rpt1[7] = 'B'; + break; + case 0x02U: + dsvt.hdr.rpt1[7] = 'C'; + break; + default: + dsvt.hdr.rpt1[7] = 'A'; + break; + } + memcpy(dsvt.hdr.rpt2, OWNER.c_str(), 7); + dsvt.hdr.rpt2[7] = 'G'; + calcPFCS(dsvt.title, 56); + if (LOG_QSO) + printf("Resetting: r1=%.8s r2=%.8s\n", dsvt.hdr.rpt1, dsvt.hdr.rpt2); + } - if (i>=0 && i<3) { - if (bool_dtmf_debug) - printf("resetting dtmf[%d] (got a header)\n", i); - dtmf_last_frame[i] = 0; - dtmf_counter[i] = 0; - memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); - dtmf_buf_count[i] = 0; + if (0==memcmp(dsvt.hdr.rpt1, OWNER.c_str(), 7) && Flag_is_ok(dsvt.hdr.flag[0])) { - /* Initialize the LAST HEARD data for the band */ + int i = dsvt.hdr.rpt1[7] - 'A'; - band_txt[i].streamID = rptrbuf.vpkt.streamid; + if (i>=0 && i<3) { + vPacketCount[i] = 0; + Index[i] = -1; + if (LOG_DTMF) + printf("resetting dtmf[%d] (got a header)\n", i); + dtmf_last_frame[i] = 0; + dtmf_counter[i] = 0; + memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); + dtmf_buf_count[i] = 0; - memcpy(band_txt[i].flags, rptrbuf.vpkt.hdr.flag, 3); + /* Initialize the LAST HEARD data for the band */ - memcpy(band_txt[i].lh_mycall, rptrbuf.vpkt.hdr.my, 8); - band_txt[i].lh_mycall[8] = '\0'; + band_txt[i].streamID = dsvt.streamid; - memcpy(band_txt[i].lh_sfx, rptrbuf.vpkt.hdr.nm, 4); - band_txt[i].lh_sfx[4] = '\0'; + memcpy(band_txt[i].flags, dsvt.hdr.flag, 3); - memcpy(band_txt[i].lh_yrcall, rptrbuf.vpkt.hdr.ur, 8); - band_txt[i].lh_yrcall[8] = '\0'; + memcpy(band_txt[i].lh_mycall, dsvt.hdr.mycall, 8); + band_txt[i].lh_mycall[8] = '\0'; - memcpy(band_txt[i].lh_rpt1, rptrbuf.vpkt.hdr.r1, 8); - band_txt[i].lh_rpt1[8] = '\0'; + memcpy(band_txt[i].lh_sfx, dsvt.hdr.sfx, 4); + band_txt[i].lh_sfx[4] = '\0'; - memcpy(band_txt[i].lh_rpt2, rptrbuf.vpkt.hdr.r2, 8); - band_txt[i].lh_rpt2[8] = '\0'; + memcpy(band_txt[i].lh_yrcall, dsvt.hdr.urcall, 8); + band_txt[i].lh_yrcall[8] = '\0'; - time(&band_txt[i].last_time); + memcpy(band_txt[i].lh_rpt1, dsvt.hdr.rpt1, 8); + band_txt[i].lh_rpt1[8] = '\0'; - band_txt[i].txt[0] = '\0'; - band_txt[i].txt_cnt = 0; - band_txt[i].txt_stats_sent = false; + memcpy(band_txt[i].lh_rpt2, dsvt.hdr.rpt2, 8); + band_txt[i].lh_rpt2[8] = '\0'; - band_txt[i].dest_rptr[0] = '\0'; + time(&band_txt[i].last_time); - /* try to process GPS mode: GPRMC and ID */ - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - band_txt[i].gprmc[0] = '\0'; - band_txt[i].gpid[0] = '\0'; - band_txt[i].is_gps_sent = false; - // band_txt[i].gps_last_time = 0; DO NOT reset it + band_txt[i].txt[0] = '\0'; + band_txt[i].txt_cnt = 0; + band_txt[i].sent_key_on_msg = false; - new_group[i] = true; - to_print[i] = 0; - ABC_grp[i] = false; + band_txt[i].dest_rptr[0] = '\0'; - band_txt[i].num_dv_frames = 0; - band_txt[i].num_dv_silent_frames = 0; - band_txt[i].num_bit_errors = 0; + /* try to process GPS mode: GPRMC and ID */ + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + band_txt[i].gprmc[0] = '\0'; + band_txt[i].gpid[0] = '\0'; + band_txt[i].is_gps_sent = false; + // band_txt[i].gps_last_time = 0; DO NOT reset it - /* select the band for aprs processing, and lock on the stream ID */ - if (bool_send_aprs) - aprs->SelectBand(i, rptrbuf.vpkt.streamid); - } - } - - /* Is MYCALL valid ? */ - memset(temp_radio_user, ' ', 8); - memcpy(temp_radio_user, rptrbuf.vpkt.hdr.my, 8); - temp_radio_user[8] = '\0'; + new_group[i] = true; + to_print[i] = 0; + ABC_grp[i] = false; - int mycall_valid = regexec(&preg, temp_radio_user, 0, NULL, 0); + band_txt[i].num_dv_frames = 0; + band_txt[i].num_dv_silent_frames = 0; + band_txt[i].num_bit_errors = 0; - if (mycall_valid == REG_NOERROR) - ; // printf("MYCALL [%s] passed IRC expression validation\n", temp_radio_user); - else { - if (mycall_valid == REG_NOMATCH) - printf("MYCALL [%s] failed IRC expression validation\n", temp_radio_user); - else - printf("Failed to validate MYCALL [%s], regexec error=%d\n", temp_radio_user, mycall_valid); + /* select the band for aprs processing, and lock on the stream ID */ + if (APRS_ENABLE) + aprs->SelectBand(i, ntohs(dsvt.streamid)); } + } - /* send data qnlink */ - if (mycall_valid == REG_NOERROR) - sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); - - if ((mycall_valid == REG_NOERROR) && - (memcmp(rptrbuf.vpkt.hdr.ur, "XRF", 3) != 0) && /* not a reflector */ - (memcmp(rptrbuf.vpkt.hdr.ur, "REF", 3) != 0) && /* not a reflector */ - (memcmp(rptrbuf.vpkt.hdr.ur, "DCS", 3) != 0) && /* not a reflector */ - (rptrbuf.vpkt.hdr.ur[0] != ' ') && /* must have something */ - (memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) != 0)) { /* urcall is NOT CQCQCQ */ - if ((rptrbuf.vpkt.hdr.ur[0] == '/') && /* urcall starts with a slash */ - (memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) == 0) && /* rpt1 is this repeater */ - ((rptrbuf.vpkt.hdr.r1[7] == 'A') || - (rptrbuf.vpkt.hdr.r1[7] == 'B') || - (rptrbuf.vpkt.hdr.r1[7] == 'C')) && /* mod is A,B,C */ - (memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) == 0) && /* rpt2 is this repeater */ - (rptrbuf.vpkt.hdr.r2[7] == 'G') && /* local Gateway */ - /*** (memcmp(rptrbuf + 44, OWNER, 7) != 0) && ***/ /* mycall is NOT this repeater */ - - ((rptrbuf.vpkt.hdr.flag[0] == 0x00) || /* normal */ - (rptrbuf.vpkt.hdr.flag[0] == 0x08) || /* EMR */ - (rptrbuf.vpkt.hdr.flag[0] == 0x20) || /* BK */ - (rptrbuf.vpkt.hdr.flag[0] == 0x28)) /* EMR + BK */ - ) { - if (memcmp(rptrbuf.vpkt.hdr.ur+1, OWNER.c_str(), 6) != 0) { /* the value after the slash in urcall, is NOT this repeater */ - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* one radio user on a repeater module at a time */ - if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { - /* YRCALL=/repeater + mod */ - /* YRCALL=/KJ4NHFB */ - - memset(temp_radio_user, ' ', 8); - memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur+1, 6); - temp_radio_user[6] = ' '; - temp_radio_user[7] = rptrbuf.vpkt.hdr.ur[7]; - if (temp_radio_user[7] == ' ') - temp_radio_user[7] = 'A'; - temp_radio_user[CALL_SIZE] = '\0'; - - bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'R'); - if (result) { /* it is a repeater */ - uint32_t address; - /* set the destination */ - to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; - memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); - to_remote_g2[i].toDst4.sin_family = AF_INET; - to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); - // if the address is in the portmap, we'll use that saved port instead of the default port - auto theAddress = portmap.find(address); - to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end()) ? g2_external.port : theAddress->second); - - memcpy(g2buf.title, "DSVT", 4); - g2buf.config = 0x10; - g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; - g2buf.id = rptrbuf.vpkt.icm_id; - g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&g2buf.streamid, &rptrbuf.vpkt.streamid, 44); - /* set rpt1 */ - memset(g2buf.hdr.rpt1, ' ', 8); - memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); - g2buf.hdr.rpt1[7] = temp_mod; - /* set rpt2 */ - memset(g2buf.hdr.rpt2, ' ', 8); - memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); - g2buf.hdr.rpt2[7] = 'G'; - /* set yrcall, can NOT let it be slash and repeater + module */ - memcpy(g2buf.hdr.urcall, "CQCQCQ ", 8); - - /* set PFCS */ - calcPFCS(g2buf.title, 56); - - // The remote repeater has been set, lets fill in the dest_rptr - // so that later we can send that to the LIVE web site - memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); - band_txt[i].dest_rptr[CALL_SIZE] = '\0'; - - // send to remote gateway - for (int j=0; j<5; j++) - sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - - printf("Routing to IP=%s port=%u, streamID=%04x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes\n", - inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), - g2buf.streamid, g2buf.hdr.mycall, - g2buf.hdr.sfx, g2buf.hdr.urcall, - g2buf.hdr.rpt1, g2buf.hdr.rpt2, - 56); - - time(&(to_remote_g2[i].last_time)); - } + /* Is MYCALL valid ? */ + memcpy(temp_radio_user, dsvt.hdr.mycall, 8); + temp_radio_user[8] = '\0'; + + bool mycall_valid = std::regex_match(temp_radio_user, preg); + + if (mycall_valid) + Gate2Link.Write(dsvt.title, recvlen); + else + printf("MYCALL [%s] failed IRC expression validation\n", temp_radio_user); + + if ( mycall_valid && + memcmp(dsvt.hdr.urcall, "XLX", 3) && // not a reflector + memcmp(dsvt.hdr.urcall, "XRF", 3) && + memcmp(dsvt.hdr.urcall, "REF", 3) && + memcmp(dsvt.hdr.urcall, "DCS", 3) && + dsvt.hdr.urcall[0]!=' ' && // must have something + memcmp(dsvt.hdr.urcall, "CQCQCQ", 6) ) // urcall is NOT CQCQCQ + { + if ( dsvt.hdr.urcall[0]=='/' && // repeater routing! + 0==memcmp(dsvt.hdr.rpt1, OWNER.c_str(), 7) && // rpt1 this repeater + (dsvt.hdr.rpt1[7]>='A' && dsvt.hdr.rpt1[7]<='C') && // with a valid module + 0==memcmp(dsvt.hdr.rpt2, OWNER.c_str(), 7) && // rpt2 is this repeater + dsvt.hdr.rpt2[7]=='G' && // local Gateway + Flag_is_ok(dsvt.hdr.flag[0]) ) + { + if (memcmp(dsvt.hdr.urcall+1, OWNER.c_str(), 6)) { // the value after the slash is NOT this repeater + int i = dsvt.hdr.rpt1[7] - 'A'; + + if (i>=0 && i<3) { + /* one radio user on a repeater module at a time */ + if (to_remote_g2[i].toDstar.AddressIsZero()) { + /* YRCALL=/repeater + mod */ + /* YRCALL=/KJ4NHFB */ + + memset(temp_radio_user, ' ', 8); + memcpy(temp_radio_user, dsvt.hdr.urcall+1, 6); + temp_radio_user[6] = ' '; + temp_radio_user[7] = dsvt.hdr.urcall[7]; + if (temp_radio_user[7] == ' ') + temp_radio_user[7] = 'A'; + temp_radio_user[CALL_SIZE] = '\0'; + + Index[i] = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'R'); + if (Index[i]--) { /* it is a repeater */ + std::string from = OWNER.substr(0, 7); + from.append(1, i+'A'); + ii[Index[i]]->sendPing(temp_radio_user, from); + to_remote_g2[i].streamid = dsvt.streamid; + if (ip.npos == ip.find(':') && af_family[Index[i]] == AF_INET6) + fprintf(stderr, "ERROR: IP returned from cache is IPV4 but family is AF_INET6!\n"); + to_remote_g2[i].toDstar.Initialize(af_family[Index[i]], (uint16_t)((af_family[Index[i]]==AF_INET6) ? g2_ipv6_external.port : g2_external.port), ip.c_str()); + + /* set rpt1 */ + memset(dsvt.hdr.rpt1, ' ', 8); + memcpy(dsvt.hdr.rpt1, arearp_cs.c_str(), arearp_cs.size()); + dsvt.hdr.rpt1[7] = temp_mod; + /* set rpt2 */ + memset(dsvt.hdr.rpt2, ' ', 8); + memcpy(dsvt.hdr.rpt2, zonerp_cs.c_str(), zonerp_cs.size()); + dsvt.hdr.rpt2[7] = 'G'; + /* set yrcall, can NOT let it be slash and repeater + module */ + memcpy(dsvt.hdr.urcall, "CQCQCQ ", 8); + + /* set PFCS */ + calcPFCS(dsvt.title, 56); + + // The remote repeater has been set, lets fill in the dest_rptr + // so that later we can send that to the LIVE web site + memcpy(band_txt[i].dest_rptr, dsvt.hdr.rpt1, 8); + band_txt[i].dest_rptr[CALL_SIZE] = '\0'; + + // send to remote gateway + for (int j=0; j<5; j++) + sendto(g2_sock[Index[i]], dsvt.title, 56, 0, to_remote_g2[i].toDstar.GetPointer(), to_remote_g2[i].toDstar.GetSize()); + + printf("id=%04x zone route to [%s]:%u ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", + ntohs(dsvt.streamid), to_remote_g2[i].toDstar.GetAddress(), to_remote_g2[i].toDstar.GetPort(), + dsvt.hdr.urcall, dsvt.hdr.rpt1, dsvt.hdr.rpt2, dsvt.hdr.mycall, dsvt.hdr.sfx); + + time(&(to_remote_g2[i].last_time)); } } } - } else if ((memcmp(rptrbuf.vpkt.hdr.ur, OWNER.c_str(), 7) != 0) && /* urcall is not this repeater */ - (memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) == 0) && /* rpt1 is this repeater */ - ((rptrbuf.vpkt.hdr.r1[7] == 'A') || - (rptrbuf.vpkt.hdr.r1[7] == 'B') || - (rptrbuf.vpkt.hdr.r1[7] == 'C')) && /* mod is A,B,C */ - (memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) == 0) && /* rpt2 is this repeater */ - (rptrbuf.vpkt.hdr.r2[7] == 'G') && /* local Gateway */ - /*** (memcmp(rptrbuf + 44, OWNER, 7) != 0) && ***/ /* mycall is NOT this repeater */ - - ((rptrbuf.vpkt.hdr.flag[0] == 0x00) || /* normal */ - (rptrbuf.vpkt.hdr.flag[0] == 0x08) || /* EMR */ - (rptrbuf.vpkt.hdr.flag[0] == 0x20) || /* BK */ - (rptrbuf.vpkt.hdr.flag[0] == 0x28)) /* EMR + BK */ - ) { - - memset(temp_radio_user, ' ', 8); - memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur, 8); - temp_radio_user[8] = '\0'; - bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'U'); - if (result) { - /* destination is a remote system */ - if (memcmp(zonerp_cs, OWNER.c_str(), 7) != 0) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* one radio user on a repeater module at a time */ - if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { - uint32_t address; - /* set the destination */ - to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; - memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); - to_remote_g2[i].toDst4.sin_family = AF_INET; - to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); - // if the address is in the portmap, we'll use that port instead of the default - auto theAddress = portmap.find(address); - to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); - - memcpy(g2buf.title, "DSVT", 4); - g2buf.config = 0x10; - g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; - g2buf.id = rptrbuf.vpkt.icm_id; - g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&g2buf.streamid, &rptrbuf.vpkt.streamid, 44); - /* set rpt1 */ - memset(g2buf.hdr.rpt1, ' ', 8); - memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); - g2buf.hdr.rpt1[7] = temp_mod; - /* set rpt2 */ - memset(g2buf.hdr.rpt2, ' ', 8); - memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); - g2buf.hdr.rpt2[7] = 'G'; - /* set PFCS */ - calcPFCS(g2buf.title, 56); - - // The remote repeater has been set, lets fill in the dest_rptr - // so that later we can send that to the LIVE web site - memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); - band_txt[i].dest_rptr[CALL_SIZE] = '\0'; - - /* send to remote gateway */ - for (int j=0; j<5; j++) - sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - - printf("Routing to IP=%s, port=%u, streamID=%04x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes\n", - inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), - g2buf.streamid, g2buf.hdr.mycall, - g2buf.hdr.sfx, g2buf.hdr.urcall, - g2buf.hdr.rpt1, g2buf.hdr.rpt2, - 56); - - time(&(to_remote_g2[i].last_time)); - } + } + } + else if (memcmp(dsvt.hdr.urcall, OWNER.c_str(), 7) && // urcall is not this repeater + 0==memcmp(dsvt.hdr.rpt1, OWNER.c_str(), 7) && // rpt1 is this repeater + (dsvt.hdr.rpt1[7]>='A'&& dsvt.hdr.rpt1[7]<='C') && // mod is A,B,C + 0==memcmp(dsvt.hdr.rpt2, OWNER.c_str(), 7) && // rpt2 is this repeater + dsvt.hdr.rpt2[7]=='G' && // local Gateway + Flag_is_ok(dsvt.hdr.flag[0])) { + + + memset(temp_radio_user, ' ', 8); + memcpy(temp_radio_user, dsvt.hdr.urcall, 8); + temp_radio_user[8] = '\0'; + int i = dsvt.hdr.rpt1[7] - 'A'; + if (i>=0 && i<3) { + Index[i] = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'U'); + if (Index[i]--) { + /* destination is a remote system */ + if (0 != zonerp_cs.compare(0, 7, OWNER, 0, 7)) { + + /* one radio user on a repeater module at a time */ + if (to_remote_g2[i].toDstar.AddressIsZero()) { + /* set the destination */ + std::string from = OWNER.substr(0, 7); + from.append(1, i+'A'); + ii[Index[i]]->sendPing(arearp_cs, from); + to_remote_g2[i].streamid = dsvt.streamid; + if (ip.npos == ip.find(':') && af_family[Index[i]] == AF_INET6) + fprintf(stderr, "ERROR: IP returned from cache is IPV4 but family is AF_INET6!\n"); + to_remote_g2[i].toDstar.Initialize(af_family[Index[i]], (uint16_t)((af_family[Index[i]]==AF_INET6) ? g2_ipv6_external.port : g2_external.port), ip.c_str()); + + /* set rpt1 */ + memset(dsvt.hdr.rpt1, ' ', 8); + memcpy(dsvt.hdr.rpt1, arearp_cs.c_str(), arearp_cs.size()); + dsvt.hdr.rpt1[7] = temp_mod; + /* set rpt2 */ + memset(dsvt.hdr.rpt2, ' ', 8); + memcpy(dsvt.hdr.rpt2, zonerp_cs.c_str(), zonerp_cs.size()); + dsvt.hdr.rpt2[7] = 'G'; + /* set PFCS */ + calcPFCS(dsvt.title, 56); + + // The remote repeater has been set, lets fill in the dest_rptr + // so that later we can send that to the LIVE web site + memcpy(band_txt[i].dest_rptr, dsvt.hdr.rpt1, 8); + band_txt[i].dest_rptr[CALL_SIZE] = '\0'; + + /* send to remote gateway */ + for (int j=0; j<5; j++) + sendto(g2_sock[Index[i]], dsvt.title, 56, 0, to_remote_g2[i].toDstar.GetPointer(), to_remote_g2[i].toDstar.GetSize()); + + printf("Callsign route to [%s]:%u id=%04x my=%.8s/%.4s ur=%.8s rpt1=%.8s rpt2=%.8s\n", to_remote_g2[i].toDstar.GetAddress(), to_remote_g2[i].toDstar.GetPort(), ntohs(dsvt.streamid), dsvt.hdr.mycall, dsvt.hdr.sfx, dsvt.hdr.urcall, dsvt.hdr.rpt1, dsvt.hdr.rpt2); + + time(&(to_remote_g2[i].last_time)); } - } else { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* the user we are trying to contact is on our gateway */ - /* make sure they are on a different module */ - if (temp_mod != rptrbuf.vpkt.hdr.r1[7]) { - /* - The remote repeater has been set, lets fill in the dest_rptr - so that later we can send that to the LIVE web site - */ - memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); - band_txt[i].dest_rptr[7] = temp_mod; - band_txt[i].dest_rptr[8] = '\0'; - - i = temp_mod - 'A'; - - /* valid destination repeater module? */ - if (i>=0 && i<3) { - /* - toRptr[i] : receiving from a remote system or cross-band - band_txt[i] : local RF is talking. - */ - if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { - printf("CALLmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], temp_mod); - - rptrbuf.vpkt.hdr.r2[7] = temp_mod; - rptrbuf.vpkt.hdr.r1[7] = 'G'; - calcPFCS(rptrbuf.pkt_id, 58); - - sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* This is the active streamid */ - toRptr[i].streamid = rptrbuf.vpkt.streamid; - toRptr[i].adr = fromRptr.sin_addr.s_addr; - - /* time it, in case stream times out */ - time(&toRptr[i].last_time); - - /* bump the G2 counter */ - toRptr[i].G2_COUNTER++; - - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - } + } + else + { + int i = dsvt.hdr.rpt1[7] - 'A'; + + if (i>=0 && i<3) { + /* the user we are trying to contact is on our gateway */ + /* make sure they are on a different module */ + if (temp_mod != dsvt.hdr.rpt1[7]) { + /* + The remote repeater has been set, lets fill in the dest_rptr + so that later we can send that to the LIVE web site + */ + memcpy(band_txt[i].dest_rptr, dsvt.hdr.rpt2, 8); + band_txt[i].dest_rptr[7] = temp_mod; + band_txt[i].dest_rptr[8] = '\0'; + + i = temp_mod - 'A'; + + /* valid destination repeater module? */ + if (i>=0 && i<3) { + /* + toRptr[i] : receiving from a remote system or cross-band + band_txt[i] : local RF is talking. + */ + if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { + printf("CALLmode cross-banding from mod %c to %c\n", dsvt.hdr.rpt1[7], temp_mod); + + dsvt.hdr.rpt2[7] = temp_mod; + dsvt.hdr.rpt1[7] = 'G'; + calcPFCS(dsvt.title, 56); + + Gate2Modem[i].Write(dsvt.title, 56); + + /* This is the active streamid */ + toRptr[i].streamid = dsvt.streamid; + + /* time it, in case stream times out */ + time(&toRptr[i].last_time); + + toRptr[i].sequence = dsvt.ctrl; + } } - } else - printf("icom rule: no routing from %.8s to %s%c\n", rptrbuf.vpkt.hdr.r1, arearp_cs, temp_mod); + } } + else + { + printf("icom rule: no routing from %.8s to %s%c\n", dsvt.hdr.rpt1, arearp_cs.c_str(), temp_mod); + } } } + else + { + if ('L' != dsvt.hdr.urcall[7]) // as long as this doesn't look like a linking command + playNotInCache = true; // we need to wait until user's transmission is over + } } - } else if ((rptrbuf.vpkt.hdr.ur[7] == '0') && - (rptrbuf.vpkt.hdr.ur[6] == 'C') && - (rptrbuf.vpkt.hdr.ur[0] == ' ')) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* voicemail file is closed */ - if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { - unlink(vm[i].file); - printf("removed voicemail file: %s\n", vm[i].file); - vm[i].file[0] = '\0'; - } else - printf("No voicemail to clear or still recording\n"); - } - } else if ((rptrbuf.vpkt.hdr.ur[7] == '0') && - (rptrbuf.vpkt.hdr.ur[6] == 'R') && - (rptrbuf.vpkt.hdr.ur[0] == ' ')) { - int i = -1; - switch (rptrbuf.vpkt.hdr.r1[7]) { - case 'A': - i = 0; - break; - case 'B': - i = 1; - break; - case 'C': - i = 2; - break; - } + } + } + else if (0 == memcmp(dsvt.hdr.urcall, " C0", 8)) { + int i = dsvt.hdr.rpt1[7] - 'A'; + + if (i>=0 && i<3) { + /* voicemail file is closed */ + if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { + unlink(vm[i].file); + printf("removed voicemail file: %s\n", vm[i].file); + vm[i].file[0] = '\0'; + } else + printf("No voicemail to clear or still recording\n"); + } + } + else if (0 == memcmp(dsvt.hdr.urcall, " R0", 8)) { + int i = dsvt.hdr.rpt1[7] - 'A'; + + if (i>=0 && i<3) { + /* voicemail file is closed */ + if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { + band_txt[i].last_time = 0; + band_txt[i].streamID = 0U; // prevent vm timeout + snprintf(vm[i].message, 21, "VOICEMAIL ON MOD %c ", 'A'+i); + try { + std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, std::ref(vm[i])); + } catch (const std::exception &e) { + printf("Failed to start voicemail playback. Exception: %s\n", e.what()); + } + } else + printf("No voicemail to recall or still recording\n"); + } + } + else if (0 == memcmp(dsvt.hdr.urcall, " S0", 8)) { + int i = dsvt.hdr.rpt1[7] - 'A'; + + if (i>=0 && i<3) { + if (vm[i].fd >= 0) + printf("Already recording for voicemail on mod %d\n", i); + else { + memset(tempfile, '\0', sizeof(tempfile)); + snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", FILE_ECHOTEST.c_str(), dsvt.hdr.rpt1[7], "voicemail.dat2"); - if (i >= 0) { - /* voicemail file is closed */ - if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { - try { - std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, vm[i].file); - } catch (const std::exception &e) { - printf("Filed to start voicemail playback. Exception: %s\n", e.what()); - } - } else - printf("No voicemail to recall or still recording\n"); - } - } else if ((rptrbuf.vpkt.hdr.ur[7] == '0') && - (rptrbuf.vpkt.hdr.ur[6] == 'S') && - (rptrbuf.vpkt.hdr.ur[0] == ' ')) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - if (vm[i].fd >= 0) - printf("Already recording for voicemail on mod %d\n", i); + vm[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (vm[i].fd < 0) + printf("Failed to create file %s for voicemail\n", tempfile); else { - memset(tempfile, '\0', sizeof(tempfile)); - snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", - echotest_dir.c_str(), - rptrbuf.vpkt.hdr.r1[7], - "voicemail.dat"); - - vm[i].fd = open(tempfile, - O_CREAT | O_WRONLY | O_TRUNC | O_APPEND, - S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (vm[i].fd < 0) - printf("Failed to create file %s for voicemail\n", tempfile); - else { - strcpy(vm[i].file, tempfile); - printf("Recording mod %c for voicemail into file:[%s]\n", - rptrbuf.vpkt.hdr.r1[7], - vm[i].file); - - time(&vm[i].last_time); - vm[i].streamid = rptrbuf.vpkt.streamid; - - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x10; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); - memset(recbuf.hdr.rpt1, ' ', 8); - memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.length()); - recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; - memset(recbuf.hdr.rpt2, ' ', 8); - memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.length()); - recbuf.hdr.rpt2[7] = 'G'; - memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); - - calcPFCS(recbuf.title, 56); - - rec_len = 56; - (void)write(vm[i].fd, "DVTOOL", 6); - (void)write(vm[i].fd, &num_recs, 4); - (void)write(vm[i].fd, &rec_len, 2); - (void)write(vm[i].fd, &recbuf, rec_len); - } + strcpy(vm[i].file, tempfile); + printf("Recording mod %c for voicemail into file:[%s]\n", dsvt.hdr.rpt1[7], vm[i].file); + + time(&vm[i].last_time); + vm[i].streamid = dsvt.streamid; + memcpy(recbuf.title, dsvt.title, 56); + memset(recbuf.hdr.rpt1, ' ', 8); + memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.size()); + recbuf.hdr.rpt1[7] = dsvt.hdr.rpt1[7]; + memset(recbuf.hdr.rpt2, ' ', 8); + memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.size()); + recbuf.hdr.rpt2[7] = 'G'; + memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); + + calcPFCS(recbuf.title, 56); + + write(vm[i].fd, recbuf.title, 56); } } - } else if (('E' == rptrbuf.vpkt.hdr.ur[7]) && (' ' == rptrbuf.vpkt.hdr.ur[0])) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + } + } + else if (0 == memcmp(dsvt.hdr.urcall, " E", 8)) { + int i = dsvt.hdr.rpt1[7] - 'A'; + + if (i>=0 && i<3) { + if (recd[i].fd >= 0) + printf("Already recording for echotest on mod %d\n", i); + else { + memset(tempfile, '\0', sizeof(tempfile)); + snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", FILE_ECHOTEST.c_str(), dsvt.hdr.rpt1[7], "echotest.dat"); - if (i>=0 && i<3) { - if (recd[i].fd >= 0) - printf("Already recording for echotest on mod %d\n", i); + recd[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_EXCL | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (recd[i].fd < 0) + printf("Failed to create file %s for echotest\n", tempfile); else { - memset(tempfile, '\0', sizeof(tempfile)); - snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), - rptrbuf.vpkt.hdr.r1[7], "echotest.dat"); - - recd[i].fd = open(tempfile, - O_CREAT | O_WRONLY | O_EXCL | O_TRUNC | O_APPEND, - S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (recd[i].fd < 0) - printf("Failed to create file %s for echotest\n", tempfile); - else { - strcpy(recd[i].file, tempfile); - printf("Recording mod %c for echotest into file:[%s]\n", - rptrbuf.vpkt.hdr.r1[7], recd[i].file); - - time(&recd[i].last_time); - recd[i].streamid = rptrbuf.vpkt.streamid; - - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x10; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); - memset(recbuf.hdr.rpt1, ' ', 8); - memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.length()); - recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; - memset(recbuf.hdr.rpt2, ' ', 8); - memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.length()); - recbuf.hdr.rpt2[7] = 'G'; - memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); - - calcPFCS(recbuf.title, 56); - - rec_len = 56; - (void)write(recd[i].fd, "DVTOOL", 6); - (void)write(recd[i].fd, &num_recs, 4); - (void)write(recd[i].fd, &rec_len, 2); - (void)write(recd[i].fd, &recbuf, rec_len); - } + strcpy(recd[i].file, tempfile); + printf("Recording mod %c for echotest into file:[%s]\n", dsvt.hdr.rpt1[7], recd[i].file); + snprintf(recd[i].message, 21, "ECHO ON MODULE %c ", 'A' + i); + time(&recd[i].last_time); + recd[i].streamid = dsvt.streamid; + + memcpy(recbuf.title, dsvt.title, 56); + memset(recbuf.hdr.rpt1, ' ', 8); + memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.length()); + recbuf.hdr.rpt1[7] = dsvt.hdr.rpt1[7]; + memset(recbuf.hdr.rpt2, ' ', 8); + memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.length()); + recbuf.hdr.rpt2[7] = 'G'; + memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); + + calcPFCS(recbuf.title, 56); + + write (recd[i].fd, recbuf.title, 56); } } - /* check for cross-banding */ - } else if (0 == (memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6)) && /* yrcall is CQCQCQ */ - (0 == memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7)) && /* rpt1 is this repeater */ - (0 == memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7)) && /* rpt2 is this repeater */ - ((rptrbuf.vpkt.hdr.r1[7] == 'A') || - (rptrbuf.vpkt.hdr.r1[7] == 'B') || - (rptrbuf.vpkt.hdr.r1[7] == 'C')) && /* mod of rpt1 is A,B,C */ - ((rptrbuf.vpkt.hdr.r2[7] == 'A') || - (rptrbuf.vpkt.hdr.r2[7] == 'B') || - (rptrbuf.vpkt.hdr.r2[7] == 'C')) && /* !!! usually a G of rpt2, but we see A,B,C */ - (rptrbuf.vpkt.hdr.r2[7] != rptrbuf.vpkt.hdr.r1[7])) { /* cross-banding? make sure NOT the same */ - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - // The remote repeater has been set, lets fill in the dest_rptr - // so that later we can send that to the LIVE web site - memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); - band_txt[i].dest_rptr[8] = '\0'; - } + } + /* check for cross-banding */ + } + else if ( 0==memcmp(dsvt.hdr.urcall, "CQCQCQ", 6) && // yrcall is CQCQCQ + 0==memcmp(dsvt.hdr.rpt2, OWNER.c_str(), 7) && // rpt1 is this repeater + 0==memcmp(dsvt.hdr.rpt1, OWNER.c_str(), 7) && // rpt2 is this repeater + (dsvt.hdr.rpt1[7]>='A' && dsvt.hdr.rpt1[7]<='C') && // mod of rpt1 is A,B,C + (dsvt.hdr.rpt2[7]>='A' && dsvt.hdr.rpt2[7]<='C') && // !!! usually G on rpt2, but we see A,B,C with + dsvt.hdr.rpt2[7]!=dsvt.hdr.rpt1[7] ) { // cross-banding? make sure NOT the same + int i = dsvt.hdr.rpt1[7] - 'A'; - i = rptrbuf.vpkt.hdr.r2[7] - 'A'; + if (i>=0 && i<3) { + // The remote repeater has been set, lets fill in the dest_rptr + // so that later we can send that to the LIVE web site + memcpy(band_txt[i].dest_rptr, dsvt.hdr.rpt2, 8); + band_txt[i].dest_rptr[8] = '\0'; + } - /* valid destination repeater module? */ - if (i>=0 && i<3) { - // toRptr[i] : receiving from a remote system or cross-band - // band_txt[i] : local RF is talking. - if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { - printf("ZONEmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], rptrbuf.vpkt.hdr.r2[7]); + i = dsvt.hdr.rpt2[7] - 'A'; - rptrbuf.vpkt.hdr.r1[7] = 'G'; - calcPFCS(rptrbuf.pkt_id, 58); + // valid destination repeater module? + if (i>=0 && i<3) { + // toRptr[i] : receiving from a remote system or cross-band + // band_txt[i] : local RF is talking. + if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { + printf("ZONEmode cross-banding from mod %c to %c\n", dsvt.hdr.rpt1[7], dsvt.hdr.rpt2[7]); - sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + dsvt.hdr.rpt1[7] = 'G'; + calcPFCS(dsvt.title, 56); - /* This is the active streamid */ - toRptr[i].streamid = rptrbuf.vpkt.streamid; - toRptr[i].adr = fromRptr.sin_addr.s_addr; + Gate2Modem[i].Write(dsvt.title, 56); - /* time it, in case stream times out */ - time(&toRptr[i].last_time); + /* This is the active streamid */ + toRptr[i].streamid = dsvt.streamid; - /* bump the G2 counter */ - toRptr[i].G2_COUNTER ++; + /* time it, in case stream times out */ + time(&toRptr[i].last_time); - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - } + toRptr[i].sequence = dsvt.ctrl; } } - } else { // recvlen != 58 - for (int i=0; i<3; i++) { - if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { - time(&band_txt[i].last_time); - - if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { // end of voice data - if (dtmf_buf_count[i] > 0) { - dtmf_file = dtmf_dir; - dtmf_file.push_back('/'); - dtmf_file.push_back('A'+i); - dtmf_file += "_mod_DTMF_NOTIFY"; - if (bool_dtmf_debug) - printf("Saving dtmfs=[%s] into file: [%s]\n", dtmf_buf[i], dtmf_file.c_str()); - FILE *dtmf_fp = fopen(dtmf_file.c_str(), "w"); - if (dtmf_fp) { - fprintf(dtmf_fp, "%s\n%s", dtmf_buf[i], band_txt[i].lh_mycall); - fclose(dtmf_fp); - } else - printf("Failed to create dtmf file %s\n", dtmf_file.c_str()); - - - if (bool_dtmf_debug) - printf("resetting dtmf[%d] (printed dtmf code %s from %s)\n", i, dtmf_buf[i], band_txt[i].lh_mycall); - memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); - dtmf_buf_count[i] = 0; - dtmf_counter[i] = 0; - dtmf_last_frame[i] = 0; - } + } + } + else + { // recvlen is 29 or 32 + for (int i=0; i<3; i++) { + if (band_txt[i].streamID == dsvt.streamid) { + time(&band_txt[i].last_time); + + if (dsvt.ctrl & 0x40) { // end of voice data + if (dtmf_buf_count[i] > 0) { + std::string dtmf_file(FILE_DTMF); + dtmf_file.push_back('/'); + dtmf_file.push_back('A'+i); + dtmf_file += "_mod_DTMF_NOTIFY"; + if (LOG_DTMF) + printf("Saving dtmfs=[%s] into file: [%s]\n", dtmf_buf[i], dtmf_file.c_str()); + FILE *dtmf_fp = fopen(dtmf_file.c_str(), "w"); + if (dtmf_fp) { + fprintf(dtmf_fp, "%s\n%s", dtmf_buf[i], band_txt[i].lh_mycall); + fclose(dtmf_fp); + } else + printf("Failed to create dtmf file %s\n", dtmf_file.c_str()); - ii->sendHeardWithTXStats(band_txt[i].lh_mycall, - band_txt[i].lh_sfx, - (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", - band_txt[i].lh_rpt1, - band_txt[i].lh_rpt2, - band_txt[i].flags[0], - band_txt[i].flags[1], - band_txt[i].flags[2], - band_txt[i].num_dv_frames, - band_txt[i].num_dv_silent_frames, - band_txt[i].num_bit_errors); - - band_txt[i].streamID = 0; - band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; - band_txt[i].lh_mycall[0] = '\0'; - band_txt[i].lh_sfx[0] = '\0'; - band_txt[i].lh_yrcall[0] = '\0'; - band_txt[i].lh_rpt1[0] = '\0'; - band_txt[i].lh_rpt2[0] = '\0'; - - band_txt[i].last_time = 0; + if (LOG_DTMF) + printf("resetting dtmf[%d] (printed dtmf code %s from %s)\n", i, dtmf_buf[i], band_txt[i].lh_mycall); + memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); + dtmf_buf_count[i] = 0; + dtmf_counter[i] = 0; + dtmf_last_frame[i] = 0; + } + if (! band_txt[i].sent_key_on_msg) { band_txt[i].txt[0] = '\0'; - band_txt[i].txt_cnt = 0; - - band_txt[i].dest_rptr[0] = '\0'; - - band_txt[i].num_dv_frames = 0; - band_txt[i].num_dv_silent_frames = 0; - band_txt[i].num_bit_errors = 0; - - } else { // not the end of the voice stream - int ber_data[3]; - int ber_errs = dstar_dv_decode(rptrbuf.vpkt.vasd.voice, ber_data); - if (ber_data[0] == 0xf85) - band_txt[i].num_dv_silent_frames++; - band_txt[i].num_bit_errors += ber_errs; - band_txt[i].num_dv_frames++; - - if ((ber_data[0] & 0x0ffc) == 0xfc0) { - dtmf_digit = (ber_data[0] & 0x03) | ((ber_data[2] & 0x60) >> 3); - if (dtmf_counter[i] > 0) { - if (dtmf_last_frame[i] != dtmf_digit) - dtmf_counter[i] = 0; - } - dtmf_last_frame[i] = dtmf_digit; - dtmf_counter[i]++; - - if ((dtmf_counter[i] == 5) && (dtmf_digit >= 0) && (dtmf_digit <= 15)) { - if (dtmf_buf_count[i] < MAX_DTMF_BUF) { - const char *dtmf_chars = "147*2580369#ABCD"; - dtmf_buf[i][ dtmf_buf_count[i] ] = dtmf_chars[dtmf_digit]; - dtmf_buf_count[i]++; - } - } - const unsigned char silence[9] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8 }; - memcpy(rptrbuf.vpkt.vasd.voice, silence, 9); - } else - dtmf_counter[i] = 0; + if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { + set_dest_rptr(i, band_txt[i].dest_rptr); + if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) + band_txt[i].dest_rptr[0] = '\0'; + } + int x = FindIndex(i); + if (x >= 0) + ii[x]->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx, (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); + band_txt[i].sent_key_on_msg = true; + } + // send the "key off" message, this will end up in the openquad.net Last Heard webpage. + int index = Index[i]; + if (index < 0) { + if (AF_INET == link_family[i]) { + index = ii[1] ? 1 : 0; + } else if (AF_INET6 == link_family[i]) { + index = 0; + } + } + if (index >= 0) + ii[index]->sendHeardWithTXStats(band_txt[i].lh_mycall, band_txt[i].lh_sfx, band_txt[i].lh_yrcall, band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].num_dv_frames, band_txt[i].num_dv_silent_frames, band_txt[i].num_bit_errors); + + if (playNotInCache) { + // Not in cache, please try again! + FILE *fp = fopen(FILE_QNVOICE_FILE.c_str(), "w"); + if (fp) { + fprintf(fp, "%c_notincache.dat_NOT_IN_CACHE\n", band_txt[i].lh_rpt1[7]); + fclose(fp); + } + playNotInCache = false; } - break; - } - } - if (recvlen == 29) - memcpy(tmp_txt, rptrbuf.vpkt.vasd.text, 3); - else - memcpy(tmp_txt, rptrbuf.vpkt.vasd1.text, 3); - - // printf("%x%x%x\n", tmp_txt[0], tmp_txt[1], tmp_txt[2]); - // printf("%c%c%c\n", tmp_txt[0] ^ 0x70, tmp_txt[1] ^ 0x4f, tmp_txt[2] ^ 0x93); - - /* extract 20-byte RADIO ID */ - if ((tmp_txt[0] != 0x55) || (tmp_txt[1] != 0x2d) || (tmp_txt[2] != 0x16)) { - // printf("%x%x%x\n", tmp_txt[0], tmp_txt[1], tmp_txt[2]); - // printf("%c%c%c\n", tmp_txt[0] ^ 0x70, tmp_txt[1] ^ 0x4f, tmp_txt[2] ^ 0x93); - - for (int i=0; i<3; i++) { - if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { - if (new_group[i]) { - tmp_txt[0] = tmp_txt[0] ^ 0x70; - header_type = tmp_txt[0] & 0xf0; - - if ((header_type == 0x50) || /* header */ - (header_type == 0xc0)) { /* squelch */ - new_group[i] = false; - to_print[i] = 0; - ABC_grp[i] = false; - } else if (header_type == 0x30) { /* GPS or GPS id or APRS */ - new_group[i] = false; - to_print[i] = tmp_txt[0] & 0x0f; - ABC_grp[i] = false; - if (to_print[i] > 5) - to_print[i] = 5; - else if (to_print[i] < 1) - to_print[i] = 1; - - if ((to_print[i] > 1) && (to_print[i] <= 5)) { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } + band_txt[i].streamID = 0; + band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; + band_txt[i].lh_mycall[0] = '\0'; + band_txt[i].lh_sfx[0] = '\0'; + band_txt[i].lh_yrcall[0] = '\0'; + band_txt[i].lh_rpt1[0] = '\0'; + band_txt[i].lh_rpt2[0] = '\0'; - /* fresh GPS string, re-initialize */ - if ((to_print[i] == 5) && ((tmp_txt[1] ^ 0x4f) == '$')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } + band_txt[i].last_time = 0; - /* do not copy CR, NL */ - if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; - band_txt[i].temp_line_cnt++; - } - if (((tmp_txt[2] ^ 0x93) != '\r') && ((tmp_txt[2] ^ 0x93) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[2] ^ 0x93; - band_txt[i].temp_line_cnt++; - } + band_txt[i].txt[0] = '\0'; + band_txt[i].txt_cnt = 0; - if (((tmp_txt[1] ^ 0x4f) == '\r') || ((tmp_txt[2] ^ 0x93) == '\r')) { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if (((tmp_txt[1] ^ 0x4f) == '\n') || ((tmp_txt[2] ^ 0x93) == '\n')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - to_print[i] -= 2; - } else { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } + band_txt[i].dest_rptr[0] = '\0'; - /* do not copy CR, NL */ - if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; - band_txt[i].temp_line_cnt++; - } + band_txt[i].num_dv_frames = 0; + band_txt[i].num_dv_silent_frames = 0; + band_txt[i].num_bit_errors = 0; + } + else + { // not the end of the voice stream + int ber_data[3]; + int ber_errs = dstar_dv_decode(dsvt.vasd.voice, ber_data); + if (ber_data[0] == 0xf85) + band_txt[i].num_dv_silent_frames++; + band_txt[i].num_bit_errors += ber_errs; + band_txt[i].num_dv_frames++; + + if ((ber_data[0] & 0x0ffc) == 0xfc0) { + dtmf_digit = (ber_data[0] & 0x03) | ((ber_data[2] & 0x60) >> 3); + if (dtmf_counter[i] > 0) { + if (dtmf_last_frame[i] != dtmf_digit) + dtmf_counter[i] = 0; + } + dtmf_last_frame[i] = dtmf_digit; + dtmf_counter[i]++; + + if ((dtmf_counter[i] == 5) && (dtmf_digit >= 0) && (dtmf_digit <= 15)) { + if (dtmf_buf_count[i] < MAX_DTMF_BUF) { + const char *dtmf_chars = "147*2580369#ABCD"; + dtmf_buf[i][ dtmf_buf_count[i] ] = dtmf_chars[dtmf_digit]; + dtmf_buf_count[i]++; + } + } + const unsigned char silence[9] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8 }; + memcpy(dsvt.vasd.voice, silence, 9); + } else + dtmf_counter[i] = 0; + } + break; + } + vPacketCount[i]++; + } + ProcessSlowData(dsvt.vasd.text, dsvt.streamid); - if ((tmp_txt[1] ^ 0x4f) == '\r') { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if ((tmp_txt[1] ^ 0x4f) == '\n') { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - to_print[i] --; - } - } else if (header_type == 0x40) { /* ABC text */ - new_group[i] = false; - to_print[i] = 3; - ABC_grp[i] = true; - C_seen[i] = ((tmp_txt[0] & 0x0f) == 0x03)?true:false; - - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[1] ^ 0x4f; - band_txt[i].txt_cnt++; - - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[2] ^ 0x93; - band_txt[i].txt_cnt++; - - /* - We should NOT see any more text, - if we already processed text, - so blank out the codes. - */ - if (band_txt[i].txt_stats_sent) { - if (recvlen == 29) { - rptrbuf.vpkt.vasd.text[0] = 0x70; - rptrbuf.vpkt.vasd.text[1] = 0x4f; - rptrbuf.vpkt.vasd.text[2] = 0x93; - } else { - rptrbuf.vpkt.vasd1.text[0] = 0x70; - rptrbuf.vpkt.vasd1.text[1] = 0x4f; - rptrbuf.vpkt.vasd1.text[2] = 0x93; - } - } + /* send data to qnlink */ + Gate2Link.Write(dsvt.title, 27); - if (band_txt[i].txt_cnt >= 20) { - band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; - /*** - ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, - band_txt[i].lh_sfx, - (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", - band_txt[i].lh_rpt1, - band_txt[i].lh_rpt2, - band_txt[i].flags[0], - band_txt[i].flags[1], - band_txt[i].flags[2], - band_txt[i].dest_rptr, - band_txt[i].txt); - ***/ - // printf("TEXT1=[%s]\n", band_txt[i].txt); - band_txt[i].txt_cnt = 0; - } - } else { - new_group[i] = false; - to_print[i] = 0; - ABC_grp[i] = false; - } - } else { - if (to_print[i] == 3) { - if (ABC_grp[i]) { - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[0] ^ 0x70; - band_txt[i].txt_cnt ++; - - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[1] ^ 0x4f; - band_txt[i].txt_cnt ++; - - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[2] ^ 0x93; - band_txt[i].txt_cnt ++; - - /* - We should NOT see any more text, - if we already processed text, - so blank out the codes. - */ - if (band_txt[i].txt_stats_sent) { - if (recvlen == 29) { - rptrbuf.vpkt.vasd.text[0] = 0x70; - rptrbuf.vpkt.vasd.text[1] = 0x4f; - rptrbuf.vpkt.vasd.text[2] = 0x93; - } else { - rptrbuf.vpkt.vasd1.text[0] = 0x70; - rptrbuf.vpkt.vasd1.text[1] = 0x4f; - rptrbuf.vpkt.vasd1.text[2] = 0x93; - } - } + /* aprs processing */ + if (APRS_ENABLE) + aprs->ProcessText(ntohs(dsvt.streamid), dsvt.ctrl, dsvt.vasd.voice); - if ((band_txt[i].txt_cnt >= 20) || C_seen[i]) { - band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; - if (!band_txt[i].txt_stats_sent) { - /*** if YRCALL is CQCQCQ, set dest_rptr ***/ - if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { - set_dest_rptr(i, band_txt[i].dest_rptr); - if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) - band_txt[i].dest_rptr[0] = '\0'; - } - - ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, - band_txt[i].lh_sfx, - (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", - band_txt[i].lh_rpt1, - band_txt[i].lh_rpt2, - band_txt[i].flags[0], - band_txt[i].flags[1], - band_txt[i].flags[2], - band_txt[i].dest_rptr, - band_txt[i].txt); - // printf("TEXT2=[%s], destination repeater=[%s]\n", band_txt[i].txt, band_txt[i].dest_rptr); - band_txt[i].txt_stats_sent = true; - } - band_txt[i].txt_cnt = 0; - } - if (C_seen[i]) - C_seen[i] = false; - } else { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } + for (int i=0; i<3; i++) { + /* find out if data must go to the remote G2 */ + if (to_remote_g2[i].streamid==dsvt.streamid && Index[i]>=0) { + sendto(g2_sock[Index[i]], dsvt.title, 27, 0, to_remote_g2[i].toDstar.GetPointer(), to_remote_g2[i].toDstar.GetSize()); - /* do not copy CR, NL */ - if (((tmp_txt[0] ^ 0x70) != '\r') && ((tmp_txt[0] ^ 0x70) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0] ^ 0x70; - band_txt[i].temp_line_cnt++; - } - if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; - band_txt[i].temp_line_cnt++; - } - if (((tmp_txt[2] ^ 0x93) != '\r') && ((tmp_txt[2] ^ 0x93) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[2] ^ 0x93; - band_txt[i].temp_line_cnt++; - } + time(&(to_remote_g2[i].last_time)); - if ( - ((tmp_txt[0] ^ 0x70) == '\r') || - ((tmp_txt[1] ^ 0x4f) == '\r') || - ((tmp_txt[2] ^ 0x93) == '\r') - ) { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if (((tmp_txt[0] ^ 0x70) == '\n') || - ((tmp_txt[1] ^ 0x4f) == '\n') || - ((tmp_txt[2] ^ 0x93) == '\n')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - } - } else if (to_print[i] == 2) { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } + /* Is this the end-of-stream */ + if (dsvt.ctrl & 0x40) { + to_remote_g2[i].toDstar.Initialize(AF_UNSPEC); + to_remote_g2[i].streamid = 0; + to_remote_g2[i].last_time = 0; + } + break; + } + else if (recd[i].fd>=0 && recd[i].streamid==dsvt.streamid) { // Is the data to be recorded for echotest + time(&recd[i].last_time); + + write(recd[i].fd, dsvt.vasd.voice, 9); + + if ((dsvt.ctrl & 0x40) != 0) { + recd[i].streamid = 0; + recd[i].last_time = 0; + close(recd[i].fd); + recd[i].fd = -1; + // printf("Closed echotest audio file:[%s]\n", recd[i].file); + + /* we are in echotest mode, so play it back */ + try { + std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, std::ref(recd[i])); + } catch (const std::exception &e) { + printf("failed to start PlayFileThread. Exception: %s\n", e.what()); + // When the echotest thread runs, it deletes the file, + // Because the echotest thread did NOT start, we delete the file here + unlink(recd[i].file); + } + } + break; + } + else if ((vm[i].fd >= 0) && (vm[i].streamid==dsvt.streamid)) { // Is the data to be recorded for voicemail + time(&vm[i].last_time); - /* do not copy CR, NL */ - if (((tmp_txt[0] ^ 0x70) != '\r') && ((tmp_txt[0] ^ 0x70) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0] ^ 0x70; - band_txt[i].temp_line_cnt++; - } - if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; - band_txt[i].temp_line_cnt++; - } + write(vm[i].fd, dsvt.vasd.voice, 9); - if (((tmp_txt[0] ^ 0x70) == '\r') || ((tmp_txt[1] ^ 0x4f) == '\r')) { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if (((tmp_txt[0] ^ 0x70) == '\n') || ((tmp_txt[1] ^ 0x4f) == '\n')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - } else if (to_print[i] == 1) { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } + if ((dsvt.ctrl & 0x40) != 0) { + vm[i].streamid = 0; + vm[i].last_time = 0; + close(vm[i].fd); + vm[i].fd = -1; + // printf("Closed voicemail audio file:[%s]\n", vm[i].file); + } + break; + } + else if (toRptr[i].streamid == dsvt.streamid) { // or maybe this is cross-banding data + Gate2Modem[i].Write(dsvt.title, 27); - /* do not copy CR, NL */ - if (((tmp_txt[0] ^ 0x70) != '\r') && ((tmp_txt[0] ^ 0x70) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0] ^ 0x70; - band_txt[i].temp_line_cnt++; - } + /* timeit */ + time(&toRptr[i].last_time); - if ((tmp_txt[0] ^ 0x70) == '\r') { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if ((tmp_txt[0] ^ 0x70) == '\n') { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - } - new_group[i] = true; - to_print[i] = 0; - ABC_grp[i] = false; - } - break; - } + toRptr[i].sequence = dsvt.ctrl; + + /* End of stream ? */ + if (dsvt.ctrl & 0x40) { + toRptr[i].last_time = 0; + toRptr[i].streamid = 0; + toRptr[i].addr.Initialize(AF_UNSPEC); } + break; } + } - /* send data to qnlink */ - sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); - - /* aprs processing */ - if (bool_send_aprs) - // streamID seq audio+text size - aprs->ProcessText(rptrbuf.vpkt.streamid, rptrbuf.vpkt.ctrl, rptrbuf.vpkt.vasd.voice, (recvlen == 29)?12:15); - - for (int i=0; i<3; i++) { - /* find out if data must go to the remote G2 */ - if (to_remote_g2[i].streamid == rptrbuf.vpkt.streamid) { - memcpy(g2buf.title, "DSVT", 4); - g2buf.config = 0x20; - g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0; - memcpy(&g2buf.id, &rptrbuf.vpkt.icm_id, 7); - if (recvlen == 29) - memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); - else - memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); + if (LOG_QSO && dsvt.ctrl&0x40U) + printf("id=%04x END RPTR\n", ntohs(dsvt.streamid)); + } + } + } +} - uint32_t address = to_remote_g2[i].toDst4.sin_addr.s_addr; - // if the address is in the portmap, we'll use that port instead of the default - auto theAddress = portmap.find(address); - to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); - sendto(g2_sock, g2buf.title, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); +void CQnetGateway::AddFDSet(int &max, int newfd, fd_set *set) +{ + if (newfd > max) + max = newfd; + FD_SET(newfd, set); +} - time(&(to_remote_g2[i].last_time)); +/* run the main loop for QnetGateway */ +void CQnetGateway::Process() +{ + // dtmf stuff initialize + for (int i=0; i<3; i++) { + dtmf_buf_count[i] = 0; + dtmf_buf[i][0] = '\0'; + dtmf_last_frame[i] = 0; + dtmf_counter[i] = 0U; + } - /* Is this the end-of-stream */ - if (rptrbuf.vpkt.ctrl & 0x40) { - memset(&to_remote_g2[i].toDst4,0,sizeof(struct sockaddr_in)); - to_remote_g2[i].streamid = 0; - to_remote_g2[i].last_time = 0; - } - break; - } else - /* Is the data to be recorded for echotest */ - if (recd[i].fd>=0 && recd[i].streamid==rptrbuf.vpkt.streamid) { - time(&recd[i].last_time); + dstar_dv_init(); - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x20; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[20] = 0; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); - if (recvlen == 29) - memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); - else - memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); - - rec_len = 27; - (void)write(recd[i].fd, &rec_len, 2); - (void)write(recd[i].fd, &recbuf, rec_len); - - if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { - recd[i].streamid = 0; - recd[i].last_time = 0; - close(recd[i].fd); - recd[i].fd = -1; - // printf("Closed echotest audio file:[%s]\n", recd[i].file); - - /* we are in echotest mode, so play it back */ - try { - std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, recd[i].file); - } catch (const std::exception &e) { - printf("failed to start PlayFileThread. Exception: %s\n", e.what()); - // When the echotest thread runs, it deletes the file, - // Because the echotest thread did NOT start, we delete the file here - unlink(recd[i].file); - } - } - break; - } else - /* Is the data to be recorded for voicemail */ - if ((vm[i].fd >= 0) && (vm[i].streamid==rptrbuf.vpkt.streamid)) { - time(&vm[i].last_time); - - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x20; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); - if (recvlen == 29) - memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); - else - memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); - - rec_len = 27; - (void)write(vm[i].fd, &rec_len, 2); - (void)write(vm[i].fd, &recbuf, rec_len); - - if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { - vm[i].streamid = 0; - vm[i].last_time = 0; - close(vm[i].fd); - vm[i].fd = -1; - // printf("Closed voicemail audio file:[%s]\n", vm[i].file); - } - break; - } else - /* or maybe this is cross-banding data */ - if ((toRptr[i].streamid==rptrbuf.vpkt.streamid) && (toRptr[i].adr == fromRptr.sin_addr.s_addr)) { - sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + std::future aprs_future, irc_data_future[2]; + if (APRS_ENABLE) { // start the beacon thread + try { + aprs_future = std::async(std::launch::async, &CQnetGateway::APRSBeaconThread, this); + } catch (const std::exception &e) { + printf("Failed to start the APRSBeaconThread. Exception: %s\n", e.what()); + } + if (aprs_future.valid()) + printf("APRS beacon thread started\n"); + } - /* timeit */ - time(&toRptr[i].last_time); + for (int i=0; i<2; i++) { + if (ii[i]) { + try { // start the IRC read thread + irc_data_future[i] = std::async(std::launch::async, &CQnetGateway::GetIRCDataThread, this, i); + } catch (const std::exception &e) { + printf("Failed to start GetIRCDataThread[%d]. Exception: %s\n", i, e.what()); + keep_running = false; + } + if (keep_running) + printf("get_irc_data thread[%d] started\n", i); - /* bump G2 counter */ - toRptr[i].G2_COUNTER ++; + ii[i]->kickWatchdog(IRCDDB_VERSION); + } + } - toRptr[i].sequence = rptrbuf.vpkt.ctrl; + while (keep_running) { + ProcessTimeouts(); - /* End of stream ? */ - if (rptrbuf.vpkt.ctrl & 0x40) { - toRptr[i].last_time = 0; - toRptr[i].streamid = 0; - toRptr[i].adr = 0; - } - break; - } - } + // wait 20 ms max + int max_nfds = 0; + fd_set fdset; + FD_ZERO(&fdset); + if (g2_sock[0] >= 0) + AddFDSet(max_nfds, g2_sock[0], &fdset); + if (g2_sock[1] >= 0) + AddFDSet(max_nfds, g2_sock[1], &fdset); + AddFDSet(max_nfds, Link2Gate.GetFD(), &fdset); + AddFDSet(max_nfds, Modem2Gate.GetFD(), &fdset); + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 20000; // 20 ms + (void)select(max_nfds + 1, &fdset, 0, 0, &tv); - if (rptrbuf.vpkt.ctrl & 0x40) { - if (bool_qso_details) - printf("END from rptr: cntr=%04x, streamID=%04x, %d bytes\n", rptrbuf.counter, rptrbuf.vpkt.streamid, recvlen); - } - } + // process packets coming from remote G2 + for (int i=0; i<2; i++) { + if (g2_sock[i] < 0) + continue; + if (keep_running && FD_ISSET(g2_sock[i], &fdset)) { + SDSVT dsvt; + socklen_t fromlen = sizeof(struct sockaddr_storage); + ssize_t g2buflen = recvfrom(g2_sock[i], dsvt.title, 56, 0, fromDstar.GetPointer(), &fromlen); + if (LOG_QSO && 4==g2buflen && 0==memcmp(dsvt.title, "PONG", 4)) + printf("Got a pong from [%s]:%u\n", fromDstar.GetAddress(), fromDstar.GetPort()); + else + ProcessG2(g2buflen, dsvt, i); + FD_CLR(g2_sock[i], &fdset); } - FD_CLR (srv_sock,&fdset); + } + + // process packets from qnlink + if (keep_running && FD_ISSET(Link2Gate.GetFD(), &fdset)) { + SDSVT dsvt; + ssize_t g2buflen = Link2Gate.Read(dsvt.title, 56); + if (16==g2buflen && 0==memcmp(dsvt.title, "LINK", 4)) { + SLINKFAMILY fam; + memcpy(fam.title, dsvt.title, 16); + if (LOG_DEBUG) { + printf("Families of linked nodes: A=AF_%s, B=AF_%s, C=AF_%s\n", + (AF_UNSPEC==fam.family[0]) ? "UNSPEC" : ((AF_INET==fam.family[0]) ? "INET" : "INET6"), + (AF_UNSPEC==fam.family[1]) ? "UNSPEC" : ((AF_INET==fam.family[1]) ? "INET" : "INET6"), + (AF_UNSPEC==fam.family[2]) ? "UNSPEC" : ((AF_INET==fam.family[2]) ? "INET" : "INET6") + ); + } + memcpy(link_family, fam.family, 12); + } else { + ProcessG2(g2buflen, dsvt, -1); + } + FD_CLR(Link2Gate.GetFD(), &fdset); + } + + // process packets coming from local repeater module(s) + if (keep_running && FD_ISSET(Modem2Gate.GetFD(), &fdset)) { + ProcessModem(); + FD_CLR(Modem2Gate.GetFD(), &fdset); } } // thread clean-up - if (bool_send_aprs) { + if (APRS_ENABLE) { if (aprs_future.valid()) aprs_future.get(); } - irc_data_future.get(); + for (int i=0; i<2; i++) { + if (ii[i]) + irc_data_future[i].get(); + } return; } @@ -2133,18 +2099,18 @@ void CQnetGateway::APRSBeaconThread() time_t last_keepalive_time; time(&last_keepalive_time); + time_t last_beacon_time = 0; /* This thread is also saying to the APRS_HOST that we are ALIVE */ while (keep_running) { - if (aprs->GetSock() == -1) { + if (aprs->aprs_sock.GetFD() == -1) { aprs->Open(OWNER); - if (aprs->GetSock() == -1) + if (aprs->aprs_sock.GetFD() == -1) sleep(1); else THRESHOLD_COUNTDOWN = 15; } time(&tnow); - time_t last_beacon_time = 0; if ((tnow - last_beacon_time) > (rptr.aprs_interval * 60)) { for (short int i=0; i<3; i++) { if (rptr.mod[i].desc[0] != '\0') { @@ -2187,14 +2153,14 @@ void CQnetGateway::APRSBeaconThread() strcat(snd_buf, "\r\n"); while (keep_running) { - if (aprs->GetSock() == -1) { + if (aprs->aprs_sock.GetFD() == -1) { aprs->Open(OWNER); - if (aprs->GetSock() == -1) + if (aprs->aprs_sock.GetFD() == -1) sleep(1); else THRESHOLD_COUNTDOWN = 15; } else { - int rc = aprs->WriteSock(snd_buf, strlen(snd_buf)); + int rc = aprs->aprs_sock.Write((unsigned char *)snd_buf, strlen(snd_buf)); if (rc < 0) { if ((errno == EPIPE) || (errno == ECONNRESET) || @@ -2208,8 +2174,7 @@ void CQnetGateway::APRSBeaconThread() (errno == EHOSTDOWN) || (errno == ENOTCONN)) { printf("send_aprs_beacon: APRS_HOST closed connection,error=%d\n",errno); - close(aprs->GetSock()); - aprs->SetSock( -1 ); + aprs->aprs_sock.Close(); } else if (errno == EWOULDBLOCK) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } else { @@ -2222,12 +2187,12 @@ void CQnetGateway::APRSBeaconThread() break; } } - int rc = recv(aprs->GetSock(), rcv_buf, sizeof(rcv_buf), 0); + int rc = aprs->aprs_sock.Read((unsigned char *)rcv_buf, sizeof(rcv_buf)); if (rc > 0) THRESHOLD_COUNTDOWN = 15; } } - int rc = recv(aprs->GetSock(), rcv_buf, sizeof(rcv_buf), 0); + int rc = aprs->aprs_sock.Read((unsigned char *)rcv_buf, sizeof(rcv_buf)); if (rc > 0) THRESHOLD_COUNTDOWN = 15; } @@ -2236,7 +2201,7 @@ void CQnetGateway::APRSBeaconThread() /* Are we still receiving from APRS host ? */ - int rc = recv(aprs->GetSock(), rcv_buf, sizeof(rcv_buf), 0); + int rc = aprs->aprs_sock.Read((unsigned char *)rcv_buf, sizeof(rcv_buf)); if (rc < 0) { if ((errno == EPIPE) || (errno == ECONNRESET) || @@ -2250,13 +2215,11 @@ void CQnetGateway::APRSBeaconThread() (errno == EHOSTDOWN) || (errno == ENOTCONN)) { printf("send_aprs_beacon: recv error: APRS_HOST closed connection,error=%d\n",errno); - close(aprs->GetSock()); - aprs->SetSock( -1 ); + aprs->aprs_sock.Close(); } } else if (rc == 0) { printf("send_aprs_beacon: recv: APRS shutdown\n"); - close(aprs->GetSock()); - aprs->SetSock( -1 ); + aprs->aprs_sock.Close(); } else THRESHOLD_COUNTDOWN = 15; @@ -2266,14 +2229,13 @@ void CQnetGateway::APRSBeaconThread() time(&tnow); if ((tnow - last_keepalive_time) > 20) { /* we should be receving keepalive packets ONLY if the connection is alive */ - if (aprs->GetSock() >= 0) { + if (aprs->aprs_sock.GetFD() >= 0) { if (THRESHOLD_COUNTDOWN > 0) THRESHOLD_COUNTDOWN--; if (THRESHOLD_COUNTDOWN == 0) { printf("APRS host keepalive timeout\n"); - close(aprs->GetSock()); - aprs->SetSock( -1 ); + aprs->aprs_sock.Close(); } } /* reset timer */ @@ -2284,14 +2246,13 @@ void CQnetGateway::APRSBeaconThread() return; } -void CQnetGateway::PlayFileThread(char *file) +void CQnetGateway::PlayFileThread(SECHO &edata) { - unsigned short rlen = 0; - unsigned char dstar_buf[56]; - unsigned char rptr_buf[58]; - short int i = 0; - struct sigaction act; + SDSVT dsvt; + const unsigned char sdsilence[3] = { 0x16U, 0x29U, 0xF5U }; + const unsigned char sdsync[3] = { 0x55U, 0x2DU, 0x16U }; + struct sigaction act; act.sa_handler = sigCatch; sigemptyset(&act.sa_mask); act.sa_flags = SA_RESTART; @@ -2308,112 +2269,142 @@ void CQnetGateway::PlayFileThread(char *file) return; } - printf("File to playback:[%s]\n", file); + printf("File to playback:[%s]\n", edata.file); - FILE *fp = fopen(file, "rb"); - if (!fp) { - printf("Failed to open file %s\n", file); + struct stat sbuf; + if (stat(edata.file, &sbuf)) { + fprintf(stderr, "Can't stat %s\n", edata.file); return; } - size_t nread = fread(dstar_buf, 10, 1, fp); - if (nread != 1) { - printf("Cant read first 10 bytes in %s\n", file); - fclose(fp); + if (sbuf.st_size < 65) { + fprintf(stderr, "Error %s file is too small!\n", edata.file); + return; + } + + if ((sbuf.st_size - 56) % 9) + printf("Warning %s file size of %ld is unexpected!\n", edata.file, sbuf.st_size); + int ambeblocks = ((int)sbuf.st_size - 56) / 9; + + FILE *fp = fopen(edata.file, "rb"); + if (!fp) { + fprintf(stderr, "Failed to open file %s\n", edata.file); return; } - if (memcmp(dstar_buf, "DVTOOL", 6) != 0) { - printf("DVTOOL keyword not found in %s\n", file); - fclose(fp); + if (1 != fread(dsvt.title, 56, 1, fp)) { + fprintf(stderr, "PlayFile Error: Can't read header from %s\n", edata.file); + fclose(fp); + return; + } + int mod = dsvt.hdr.rpt1[7] - 'A'; + if (mod<0 || mod>2) { + fprintf(stderr, "unknown module suffix '%s'\n", dsvt.hdr.rpt1); return; } - sleep(play_wait); - while (keep_running) { - nread = fread(&rlen, 2, 1, fp); - if (nread != 1) - break; + sleep(TIMING_PLAY_WAIT); - if ((rlen != 56) && (rlen != 27)) { - printf("Expected 56 bytes or 27 bytes, found %d\n", rlen); - break; - } - nread = fread(dstar_buf, rlen, 1, fp); - if (nread == 1) { - if (memcmp(dstar_buf, "DSVT", 4) != 0) { - printf("DVST keyword not found in %s\n", file); - break; - } + // reformat and send it + memcpy(dsvt.hdr.urcall, "CQCQCQ ", 8); + calcPFCS(dsvt.title, 56); - if (dstar_buf[8] != 0x20) { - printf("Not Voice type in %s\n", file); - break; - } + Gate2Modem[mod].Write(dsvt.title, 56); - if ((dstar_buf[4] != 0x10) && (dstar_buf[4] != 0x20)) { - printf("Not a valid record type in %s\n",file); - break; - } + dsvt.config = 0x20U; + + for (int i=0; i> 8) & 0xff); - rptr_buf[6] = 0x73; - rptr_buf[7] = 0x12; - rptr_buf[8] = 0x00; - rptr_buf[9] = 0x30; - rptr_buf[10] = 0x20; - memcpy(rptr_buf + 11, dstar_buf + 9, 47); - - /* We did not change anything */ - // calcPFCS(rptr_buf, 58); + int nread = fread(dsvt.vasd.voice, 9, 1, fp); + if (nread == 1) { + dsvt.ctrl = (unsigned char)(i % 21); + if (0x0U == dsvt.ctrl) { + memcpy(dsvt.vasd.text, sdsync, 3); } else { - rptr_buf[5] = (unsigned char)(toRptr[i].G2_COUNTER & 0xff); - rptr_buf[4] = (unsigned char)((toRptr[i].G2_COUNTER >> 8) & 0xff); - rptr_buf[9] = 0x13; - memcpy(rptr_buf + 11, dstar_buf + 9, 18); + switch (i) { + case 1: + dsvt.vasd.text[0] = '@' ^ 0x70; + dsvt.vasd.text[1] = edata.message[0] ^ 0x4f; + dsvt.vasd.text[2] = edata.message[1] ^ 0x93; + break; + case 2: + dsvt.vasd.text[0] = edata.message[2] ^ 0x70; + dsvt.vasd.text[1] = edata.message[3] ^ 0x4f; + dsvt.vasd.text[2] = edata.message[4] ^ 0x93; + break; + case 3: + dsvt.vasd.text[0] = 'A' ^ 0x70; + dsvt.vasd.text[1] = edata.message[5] ^ 0x4f; + dsvt.vasd.text[2] = edata.message[6] ^ 0x93; + break; + case 4: + dsvt.vasd.text[0] = edata.message[7] ^ 0x70; + dsvt.vasd.text[1] = edata.message[8] ^ 0x4f; + dsvt.vasd.text[2] = edata.message[9] ^ 0x93; + break; + case 5: + dsvt.vasd.text[0] = 'B' ^ 0x70; + dsvt.vasd.text[1] = edata.message[10] ^ 0x4f; + dsvt.vasd.text[2] = edata.message[11] ^ 0x93; + break; + case 6: + dsvt.vasd.text[0] = edata.message[12] ^ 0x70; + dsvt.vasd.text[1] = edata.message[13] ^ 0x4f; + dsvt.vasd.text[2] = edata.message[14] ^ 0x93; + break; + case 7: + dsvt.vasd.text[0] = 'C' ^ 0x70; + dsvt.vasd.text[1] = edata.message[15] ^ 0x4f; + dsvt.vasd.text[2] = edata.message[16] ^ 0x93; + break; + case 8: + dsvt.vasd.text[0] = edata.message[17] ^ 0x70; + dsvt.vasd.text[1] = edata.message[18] ^ 0x4f; + dsvt.vasd.text[2] = edata.message[19] ^ 0x93; + break; + default: + memcpy(dsvt.vasd.text, sdsilence, 3); + break; + } } + if (i+1 == ambeblocks) + dsvt.ctrl |= 0x40U; - sendto(srv_sock, rptr_buf, rlen + 2, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + Gate2Modem[mod].Write(dsvt.title, 27); - toRptr[i].G2_COUNTER ++; - - std::this_thread::sleep_for(std::chrono::milliseconds(play_delay)); + std::this_thread::sleep_for(std::chrono::milliseconds(TIMING_PLAY_DELAY)); } } fclose(fp); - if (!strstr(file, "voicemail.dat")) - unlink(file); printf("Finished playing\n"); + // if it's an echo file, delete it! + if (strstr(edata.file, "echotest.dat")) { + unlink(edata.file); + edata.file[0] = edata.message[0] = '\0'; + } return; } void CQnetGateway::qrgs_and_maps() { - for(int i=0; i<3; i++) { + for (int i=0; i<3; i++) { std::string rptrcall = OWNER; rptrcall.resize(CALL_SIZE-1); rptrcall += i + 'A'; - if (rptr.mod[i].latitude || rptr.mod[i].longitude || rptr.mod[i].desc1.length() || rptr.mod[i].url.length()) - ii->rptrQTH(rptrcall, rptr.mod[i].latitude, rptr.mod[i].longitude, rptr.mod[i].desc1, rptr.mod[i].desc2, rptr.mod[i].url, rptr.mod[i].package_version); - if (rptr.mod[i].frequency) - ii->rptrQRG(rptrcall, rptr.mod[i].frequency, rptr.mod[i].offset, rptr.mod[i].range, rptr.mod[i].agl); + for (int j=0; j<2; j++) { + if (ii[j]) { + if (rptr.mod[i].latitude || rptr.mod[i].longitude || rptr.mod[i].desc1.length() || rptr.mod[i].url.length()) + ii[j]->rptrQTH(rptrcall, rptr.mod[i].latitude, rptr.mod[i].longitude, rptr.mod[i].desc1, rptr.mod[i].desc2, rptr.mod[i].url, rptr.mod[i].package_version); + if (rptr.mod[i].frequency) + ii[j]->rptrQRG(rptrcall, rptr.mod[i].frequency, rptr.mod[i].offset, rptr.mod[i].range, rptr.mod[i].agl); + } + } } return; } -int CQnetGateway::init(char *cfgfile) +bool CQnetGateway::Init(char *cfgfile) { short int i; struct sigaction act; @@ -2422,10 +2413,11 @@ int CQnetGateway::init(char *cfgfile) /* Used to validate MYCALL input */ - int rc = regcomp(&preg, "^(([1-9][A-Z])|([A-Z][0-9])|([A-Z][A-Z][0-9]))[0-9A-Z]*[A-Z][ ]*[ A-RT-Z]$", REG_EXTENDED | REG_NOSUB); - if (rc != REG_NOERROR) { - printf("The IRC regular expression is NOT valid\n"); - return 1; + try { + preg = std::regex("^(([1-9][A-Z])|([A-Z][0-9])|([A-Z][A-Z][0-9]))[0-9A-Z]*[A-Z][ ]*[ A-RT-Z]$", std::regex::extended); + } catch (std::regex_error &e) { + printf("Regular expression error: %s\n", e.what()); + return true; } act.sa_handler = sigCatch; @@ -2433,31 +2425,28 @@ int CQnetGateway::init(char *cfgfile) act.sa_flags = SA_RESTART; if (sigaction(SIGTERM, &act, 0) != 0) { printf("sigaction-TERM failed, error=%d\n", errno); - return 1; + return true; } if (sigaction(SIGINT, &act, 0) != 0) { printf("sigaction-INT failed, error=%d\n", errno); - return 1; + return true; } if (sigaction(SIGPIPE, &act, 0) != 0) { printf("sigaction-PIPE failed, error=%d\n", errno); - return 1; + return true; } - rptr.mod[0].band = "23cm"; - rptr.mod[1].band = "70cm"; - rptr.mod[2].band = "2m"; - - for (i = 0; i < 3; i++) { + for (i = 0; i < 3; i++) memset(&band_txt[0], 0, sizeof(SBANDTXT)); - } /* process configuration file */ - if ( read_config(cfgfile) ) { + if ( ReadConfig(cfgfile) ) { printf("Failed to process config file %s\n", cfgfile); - return 1; + return true; } + playNotInCache = false; + /* build the repeater callsigns for aprs */ rptr.mod[0].call = OWNER; for (i=OWNER.length(); i; i--) @@ -2470,62 +2459,133 @@ int CQnetGateway::init(char *cfgfile) rptr.mod[0].call += "-A"; rptr.mod[1].call += "-B"; rptr.mod[2].call += "-C"; + rptr.mod[0].band = "23cm"; + rptr.mod[1].band = "70cm"; + rptr.mod[2].band = "2m"; printf("Repeater callsigns: [%s] [%s] [%s]\n", rptr.mod[0].call.c_str(), rptr.mod[1].call.c_str(), rptr.mod[2].call.c_str()); - if (bool_send_aprs) { + for (i = 0; i < 3; i++) { + //rptr.mod[i].frequency = rptr.mod[i].offset = rptr.mod[i].latitude = rptr.mod[i].longitude = rptr.mod[i].agl = rptr.mod[i].range = 0.0; + band_txt[i].streamID = 0; + band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; + band_txt[i].lh_mycall[0] = '\0'; + band_txt[i].lh_sfx[0] = '\0'; + band_txt[i].lh_yrcall[0] = '\0'; + band_txt[i].lh_rpt1[0] = '\0'; + band_txt[i].lh_rpt2[0] = '\0'; + + band_txt[i].last_time = 0; + + band_txt[i].txt[0] = '\0'; + band_txt[i].txt_cnt = 0; + band_txt[i].sent_key_on_msg = false; + + band_txt[i].dest_rptr[0] = '\0'; + + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + band_txt[i].gprmc[0] = '\0'; + band_txt[i].gpid[0] = '\0'; + band_txt[i].is_gps_sent = false; + band_txt[i].gps_last_time = 0; + + band_txt[i].num_dv_frames = 0; + band_txt[i].num_dv_silent_frames = 0; + band_txt[i].num_bit_errors = 0; + + } + + if (APRS_ENABLE) { aprs = new CAPRS(&rptr); if (aprs) aprs->Init(); else { printf("aprs class init failed!\nAPRS will be turned off"); - bool_send_aprs = false; + APRS_ENABLE = false; } } compute_aprs_hash(); - ii = new CIRCDDB(ircddb.ip, ircddb.port, owner, irc_pass, IRCDDB_VERSION, local_irc_ip); - bool ok = ii->open(); - if (!ok) { - printf("irc open failed\n"); - return 1; - } + for (int j=0; j<2; j++) { + if (ircddb[j].ip.empty()) + continue; + ii[j] = new CIRCDDB(ircddb[j].ip, ircddb[j].port, owner, IRCDDB_PASSWORD[j], IRCDDB_VERSION.c_str()); + bool ok = ii[j]->open(); + if (!ok) { + printf("%s open failed\n", ircddb[j].ip.c_str()); + return true; + } - rc = ii->getConnectionState(); - printf("Waiting for irc connection status of 2\n"); - i = 0; - while (rc < 2) { - printf("irc status=%d\n", rc); - if (rc < 2) { - i++; - sleep(5); - } else - break; + int rc = ii[j]->getConnectionState(); + printf("Waiting for %s connection status of 2\n", ircddb[j].ip.c_str()); + i = 0; + while (rc < 2) { + printf("%s status=%d\n", ircddb[j].ip.c_str(), rc); + if (rc < 2) { + i++; + sleep(5); + } else + break; - if (!keep_running) - break; + if (!keep_running) + break; - if (i > 5) { - printf("We can not wait any longer...\n"); + if (i > 5) { + printf("We can not wait any longer for %s...\n", ircddb[j].ip.c_str()); + break; + } + rc = ii[j]->getConnectionState(); + } + switch (ii[j]->GetFamily()) { + case AF_INET: + printf("IRC server is using IPV4\n"); + af_family[j] = AF_INET; break; + case AF_INET6: + printf("IRC server is using IPV6\n"); + af_family[j] = AF_INET6; + break; + default: + printf("%s server is using unknown protocol! Shutting down...\n", ircddb[j].ip.c_str()); + return true; } - rc = ii->getConnectionState(); } - - /* udp port 40000 must open first */ - g2_sock = open_port(g2_external); - if (0 > g2_sock) { - printf("Can't open %s:%d\n", g2_external.ip.c_str(), g2_external.port); - return 1; + /* udp port 40000 must open first */ + if (ii[0]) { + SPORTIP *pip = (AF_INET == af_family[0]) ? &g2_external : & g2_ipv6_external; + g2_sock[0] = open_port(pip, af_family[0]); + if (0 > g2_sock[0]) { + printf("Can't open %s:%d for %s\n", pip->ip.c_str(), pip->port, ircddb[i].ip.c_str()); + return true; + } + if (ii[1] && (af_family[0] != af_family[1])) { // we only need to open a second port if the family for the irc servers are different! + SPORTIP *pip = (AF_INET == af_family[1]) ? &g2_external : & g2_ipv6_external; + g2_sock[1] = open_port(pip, af_family[1]); + if (0 > g2_sock[1]) { + printf("Can't open %s:%d for %s\n", pip->ip.c_str(), pip->port, ircddb[1].ip.c_str()); + return true; + } + } + } else if (ii[1]) { + SPORTIP *pip = (AF_INET == af_family[1]) ? &g2_external : & g2_ipv6_external; + g2_sock[1] = open_port(pip, af_family[1]); + if (0 > g2_sock[1]) { + printf("Can't open %s:%d for %s\n", pip->ip.c_str(), pip->port, ircddb[1].ip.c_str()); + return true; + } } - /* Open udp INTERNAL port (default: 19000) */ - srv_sock = open_port(g2_internal); - if (0 > srv_sock) { - printf("Can't open %s:%d\n", g2_internal.ip.c_str(), g2_internal.port); - return 1; - } + // Open unix sockets between qngateway and qnlink + Gate2Link.SetUp(gate2link.c_str()); + if (Link2Gate.Open(link2gate.c_str())) + return true; + if (Modem2Gate.Open(modem2gate.c_str())) + return true; - for (i = 0; i < 3; i++) { + for (i=0; i<3; i++) { + if (rptr.mod[i].defined) { // open unix sockets between qngateway and each defined modem + Gate2Modem[i].SetUp(gate2modem[i].c_str()); + } // recording for echotest on local repeater modules recd[i].last_time = 0; recd[i].streamid = 0; @@ -2538,7 +2598,7 @@ int CQnetGateway::init(char *cfgfile) vm[i].fd = -1; memset(vm[i].file, 0, sizeof(vm[i].file)); - snprintf(vm[i].file, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), 'A'+i, "voicemail.dat"); + snprintf(vm[i].file, FILENAME_MAX, "%s/%c_%s", FILE_ECHOTEST.c_str(), 'A'+i, "voicemail.dat2"); if (access(vm[i].file, F_OK) != 0) memset(vm[i].file, 0, sizeof(vm[i].file)); @@ -2546,20 +2606,13 @@ int CQnetGateway::init(char *cfgfile) printf("Loaded voicemail file: %s for mod %d\n", vm[i].file, i); // the repeater modules run on these ports - memset(&toRptr[i],0,sizeof(toRptr[i])); - - memset(toRptr[i].saved_hdr, 0, sizeof(toRptr[i].saved_hdr)); - toRptr[i].saved_adr = 0; + memset(toRptr[i].saved_hdr.title, 0, 56); + toRptr[i].saved_addr.Initialize(AF_UNSPEC); toRptr[i].streamid = 0; - toRptr[i].adr = 0; - - toRptr[i].band_addr.sin_family = AF_INET; - toRptr[i].band_addr.sin_addr.s_addr = inet_addr(rptr.mod[i].portip.ip.c_str()); - toRptr[i].band_addr.sin_port = htons(rptr.mod[i].portip.port); + toRptr[i].addr.Initialize(AF_UNSPEC); toRptr[i].last_time = 0; - toRptr[i].G2_COUNTER = 0; toRptr[i].sequence = 0x0; } @@ -2568,58 +2621,37 @@ int CQnetGateway::init(char *cfgfile) Initialize the end_of_audio that will be sent to the local repeater when audio from remote G2 has timed out */ - memcpy(end_of_audio.pkt_id, "DSTR", 4); - end_of_audio.flag[0] = 0x73; - end_of_audio.flag[1] = 0x12; - end_of_audio.flag[2] = 0x00; - end_of_audio.remaining = 0x13; - end_of_audio.vpkt.icm_id = 0x20; - end_of_audio.vpkt.dst_rptr_id = 0x00; - end_of_audio.vpkt.snd_rptr_id = 0x01; - memset(end_of_audio.vpkt.vasd.voice, '\0', 9); - end_of_audio.vpkt.vasd.text[0] = 0x70; - end_of_audio.vpkt.vasd.text[1] = 0x4f; - end_of_audio.vpkt.vasd.text[2] = 0x93; + memset(end_of_audio.title, 0U, 27U); + memcpy(end_of_audio.title, "DSVT", 4U); + end_of_audio.id = end_of_audio.config = 0x20U; /* to remote systems */ for (i = 0; i < 3; i++) { - memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); + to_remote_g2[i].toDstar.Initialize(AF_UNSPEC); to_remote_g2[i].streamid = 0; to_remote_g2[i].last_time = 0; } - /* where to send packets to qnlink */ - memset(&plug, 0, sizeof(struct sockaddr_in)); - plug.sin_family = AF_INET; - plug.sin_port = htons(g2_link.port); - plug.sin_addr.s_addr = inet_addr(g2_link.ip.c_str()); - printf("QnetGateway...entering processing loop\n"); - if (bool_send_qrgs) + if (GATEWAY_SEND_QRGS_MAP) qrgs_and_maps(); - return 0; + return false; } CQnetGateway::CQnetGateway() { + ii[0] = ii[1] = NULL; } CQnetGateway::~CQnetGateway() { - if (srv_sock != -1) { - close(srv_sock); - printf("Closed G2_INTERNAL_PORT\n"); - } - - if (g2_sock != -1) { - close(g2_sock); - printf("Closed G2_EXTERNAL_PORT\n"); - } + Link2Gate.Close(); + Modem2Gate.Close(); - if (bool_send_aprs) { - if (aprs->GetSock() != -1) { - close(aprs->GetSock()); + if (APRS_ENABLE) { + if (aprs->aprs_sock.GetFD() != -1) { + aprs->aprs_sock.Close(); printf("Closed APRS\n"); } delete aprs; @@ -2634,8 +2666,16 @@ CQnetGateway::~CQnetGateway() } } - ii->close(); - delete ii; + for (int i=0; i<2; i++) { + if (g2_sock[i] >= 0) { + close(g2_sock[i]); + printf("Closed G2_EXTERNAL_PORT %d\n", i); + } + if (ii[i]) { + ii[i]->close(); + delete ii[i]; + } + } printf("QnetGateway exiting\n"); } @@ -2684,8 +2724,7 @@ void CQnetGateway::gps_send(short int rptr_idx) return; } if (memcmp(band_txt[rptr_idx].gpid, band_txt[rptr_idx].lh_mycall, CALL_SIZE) != 0) { - printf("MYCALL [%s] does not match first 8 characters of GPS ID [%.8s]\n", - band_txt[rptr_idx].lh_mycall, band_txt[rptr_idx].gpid); + printf("MYCALL [%s] does not match first 8 characters of GPS ID [%.8s]\n", band_txt[rptr_idx].lh_mycall, band_txt[rptr_idx].gpid); band_txt[rptr_idx].gprmc[0] = '\0'; band_txt[rptr_idx].gpid[0] = '\0'; return; @@ -2705,7 +2744,7 @@ void CQnetGateway::gps_send(short int rptr_idx) printf("GPRMC=[%s]\n", band_txt[rptr_idx].gprmc); printf("GPS id=[%s]\n",band_txt[rptr_idx].gpid); - if (validate_csum(band_txt[rptr_idx], false) || validate_csum(band_txt[rptr_idx], true)) + if (validate_csum(band_txt[rptr_idx], false)) // || validate_csum(band_txt[rptr_idx], true)) return; /* now convert GPS into APRS and send it */ @@ -2760,7 +2799,7 @@ void CQnetGateway::build_aprs_from_gps_and_send(short int rptr_idx) printf("Invalid North or South indicator in latitude\n"); return; } - if (strlen(lat_str) != 9) { + if (strlen(lat_str) > 9) { printf("Invalid latitude\n"); return; } @@ -2776,14 +2815,14 @@ void CQnetGateway::build_aprs_from_gps_and_send(short int rptr_idx) return; } /* secondary table */ - strcat(buf, "\\"); + strcat(buf, "/"); if (lon_str && lon_EW) { if ((*lon_EW != 'E') && (*lon_EW != 'W')) { printf("Invalid East or West indicator in longitude\n"); return; } - if (strlen(lon_str) != 10) { + if (strlen(lon_str) > 10) { printf("Invalid longitude\n"); return; } @@ -2800,19 +2839,18 @@ void CQnetGateway::build_aprs_from_gps_and_send(short int rptr_idx) } /* Just this symbolcode only */ - strcat(buf, "k"); + strcat(buf, "/"); strncat(buf, band_txt[rptr_idx].gpid + 13, 32); // printf("Built APRS from old GPS mode=[%s]\n", buf); strcat(buf, "\r\n"); - if (-1 == aprs->WriteSock(buf, strlen(buf))) { + if (aprs->aprs_sock.Write((unsigned char *)buf, strlen(buf))) { if ((errno == EPIPE) || (errno == ECONNRESET) || (errno == ETIMEDOUT) || (errno == ECONNABORTED) || (errno == ESHUTDOWN) || (errno == EHOSTUNREACH) || (errno == ENETRESET) || (errno == ENETDOWN) || (errno == ENETUNREACH) || (errno == EHOSTDOWN) || (errno == ENOTCONN)) { printf("build_aprs_from_gps_and_send: APRS_HOST closed connection, error=%d\n", errno); - close(aprs->GetSock()); - aprs->SetSock( -1 ); + aprs->aprs_sock.Close(); } else printf("build_aprs_from_gps_and_send: send error=%d\n", errno); } @@ -2847,14 +2885,16 @@ bool CQnetGateway::verify_gps_csum(char *gps_text, char *csum_text) int main(int argc, char **argv) { - printf("VERSION %s\n", IRCDDB_VERSION); + printf("VERSION %s\n", IRCDDB_VERSION.c_str()); if (argc != 2) { printf("usage: %s qn.cfg\n", argv[0]); return 1; } CQnetGateway QnetGateway; - if (QnetGateway.init(argv[1])) + if (QnetGateway.Init(argv[1])) { + keep_running = false; // shutdown any processes that may have been started return 1; - QnetGateway.process(); + } + QnetGateway.Process(); printf("Leaving processing loop...\n"); } diff --git a/QnetGateway.h b/QnetGateway.h index 17adabe..04dcc7b 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 by Thomas Early N7TAE + * Copyright (C) 2018-2019 by Thomas Early N7TAE * * 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 @@ -16,40 +16,33 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#include +#include +#include +#include "QnetTypeDefs.h" +#include "SEcho.h" +#include "UnixDgramSocket.h" #include "aprs.h" +#include "SockAddress.h" -using namespace libconfig; - -#define IP_SIZE 15 #define MAXHOSTNAMELEN 64 #define CALL_SIZE 8 #define MAX_DTMF_BUF 32 -typedef struct echo_tag { - time_t last_time; - unsigned short streamid; - int fd; - char file[FILENAME_MAX + 1]; -} SECHO; - typedef struct to_remote_g2_tag { unsigned short streamid; - struct sockaddr_in toDst4; + CSockAddress toDstar; time_t last_time; } STOREMOTEG2; typedef struct torepeater_tag { // help with header re-generation - unsigned char saved_hdr[58]; // repeater format - uint32_t saved_adr; + SDSVT saved_hdr; // repeater format + CSockAddress saved_addr; unsigned short streamid; - uint32_t adr; - struct sockaddr_in band_addr; + CSockAddress addr; time_t last_time; - std::atomic G2_COUNTER; unsigned char sequence; } STOREPEATER; @@ -64,7 +57,7 @@ typedef struct band_txt_tag { time_t last_time; char txt[64]; // Only 20 are used unsigned short txt_cnt; - bool txt_stats_sent; + bool sent_key_on_msg; char dest_rptr[CALL_SIZE + 1]; @@ -85,16 +78,39 @@ class CQnetGateway { public: CQnetGateway(); ~CQnetGateway(); + void Process(); + bool Init(char *cfgfile); + private: - SPORTIP g2_internal, g2_external, g2_link, ircddb; + // link type + int link_family[3] = { AF_UNSPEC, AF_UNSPEC, AF_UNSPEC }; + // network type + int af_family[2] = { AF_UNSPEC, AF_UNSPEC }; + // text stuff + bool new_group[3] = { true, true, true }; + unsigned char header_type = 0; + short to_print[3] = { 0, 0, 0 }; + bool ABC_grp[3] = { false, false, false }; + bool C_seen[3] = { false, false, false }; + int Index[3] = { -1, -1, -1 }; + + SPORTIP g2_external, g2_ipv6_external, ircddb[2]; + + CUnixDgramReader Link2Gate, Modem2Gate; + CUnixDgramWriter Gate2Link, Gate2Modem[3]; - std::string OWNER, owner, local_irc_ip, status_file, dtmf_dir, dtmf_file, echotest_dir, irc_pass; + std::string gate2link, link2gate, gate2modem[3], modem2gate; - bool bool_send_qrgs, bool_irc_debug, bool_dtmf_debug, bool_regen_header, bool_qso_details, bool_send_aprs; + std::string OWNER, owner, FILE_STATUS, FILE_DTMF, FILE_ECHOTEST, IRCDDB_PASSWORD[2], FILE_QNVOICE_FILE; - int play_wait, play_delay, echotest_rec_timeout, voicemail_rec_timeout, from_remote_g2_timeout, from_local_rptr_timeout, dtmf_digit; + bool GATEWAY_SEND_QRGS_MAP, GATEWAY_HEADER_REGEN, APRS_ENABLE, playNotInCache; + bool LOG_DEBUG, LOG_IRC, LOG_DTMF, LOG_QSO; - std::map portmap; + int TIMING_PLAY_WAIT, TIMING_PLAY_DELAY, TIMING_TIMEOUT_ECHO, TIMING_TIMEOUT_VOICEMAIL, TIMING_TIMEOUT_REMOTE_G2, TIMING_TIMEOUT_LOCAL_RPTR, dtmf_digit; + + unsigned int vPacketCount[3] = { 0, 0, 0 }; + + std::set findRoute; // data needed for aprs login and aprs beacon // RPTR defined in aprs.h @@ -109,25 +125,20 @@ private: STOREMOTEG2 to_remote_g2[3]; // 0=A, 1=B, 2=C // input from remote G2 gateway - int g2_sock = -1; - struct sockaddr_in fromDst4; + int g2_sock[2] = { -1, -1 }; + CSockAddress fromDstar; // Incoming data from remote systems // must be fed into our local repeater modules. STOREPEATER toRptr[3]; // 0=A, 1=B, 2=C - // input from our own local repeater modules - int srv_sock = -1; - SDSTR rptrbuf; // 58 or 29 or 32, max is 58 - struct sockaddr_in fromRptr; - - SDSTR end_of_audio; + SDSVT end_of_audio; // send packets to g2_link struct sockaddr_in plug; // for talking with the irc server - CIRCDDB *ii; + CIRCDDB *ii[2]; // for handling APRS stuff CAPRS *aprs; @@ -135,30 +146,42 @@ private: SBANDTXT band_txt[3]; // 0=A, 1=B, 2=C /* Used to validate MYCALL input */ - regex_t preg; + std::regex preg; // CACHE used to cache users, repeaters, // gateways, IP numbers coming from the irc server - std::map user2rptr_map, rptr2gwy_map, gwy2ip_map; + std::map user2rptr_map[2], rptr2gwy_map[2], gwy2ip_map[2]; - pthread_mutex_t irc_data_mutex = PTHREAD_MUTEX_INITIALIZER; + pthread_mutex_t irc_data_mutex[2] = PTHREAD_MUTEX_INITIALIZER; - int open_port(const SPORTIP &pip); + // dtmf stuff + int dtmf_buf_count[3]; + char dtmf_buf[3][MAX_DTMF_BUF + 1]; + int dtmf_last_frame[3]; + unsigned int dtmf_counter[3]; + + bool VoicePacketIsSync(const unsigned char *text); + void AddFDSet(int &max, int newfd, fd_set *set); + int open_port(const SPORTIP *pip, int family); void calcPFCS(unsigned char *packet, int len); - void GetIRCDataThread(); - int get_yrcall_rptr_from_cache(char *call, char *arearp_cs, char *zonerp_cs, char *mod, char *ip, char RoU); - bool get_yrcall_rptr(char *call, char *arearp_cs, char *zonerp_cs, char *mod, char *ip, char RoU); - void PlayFileThread(char *file); + void GetIRCDataThread(const int i); + int get_yrcall_rptr_from_cache(const int i, const std::string &call, std::string &arearp_cs, std::string &zonerp_cs, char *mod, std::string &ip, char RoU); + int get_yrcall_rptr(const std::string &call, std::string &arearp_cs, std::string &zonerp_cs, char *mod, std::string &ip, char RoU); + void PlayFileThread(SECHO &edata); void compute_aprs_hash(); void APRSBeaconThread(); + void ProcessTimeouts(); + void ProcessSlowData(unsigned char *data, unsigned short sid); + void ProcessG2(const ssize_t g2buflen, const SDSVT &g2buf, const int sock_source); + void ProcessModem(); + bool Flag_is_ok(unsigned char flag); + void UnpackCallsigns(const std::string &str, std::set &set, const std::string &delimiters = ","); + void PrintCallsigns(const std::string &key, const std::set &set); + int FindIndex(const int i) const; // read configuration file - bool read_config(char *); - bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value); - bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value); - bool get_value(const Config &cfg, const char *path, bool &value, bool default_value); - bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value); + bool ReadConfig(char *); /* aprs functions, borrowed from my retired IRLP node 4201 */ void gps_send(short int rptr_idx); @@ -169,8 +192,4 @@ private: void set_dest_rptr(int mod_ndx, char *dest_rptr); bool validate_csum(SBANDTXT &bt, bool is_gps); - -public: - void process(); - int init(char *cfgfile); }; diff --git a/QnetITAP.cpp b/QnetITAP.cpp new file mode 100644 index 0000000..1640247 --- /dev/null +++ b/QnetITAP.cpp @@ -0,0 +1,722 @@ +/* + * Copyright (C) 2018-2019 by Thomas A. Early N7TAE + * + * CQnetITAP::GetITAPData() is based on some code that is... + * Copyright (C) 2011-2015,2018 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "QnetITAP.h" +#include "QnetTypeDefs.h" +#include "QnetConfigure.h" +#include "Timer.h" + +#define ITAP_VERSION "QnetITAP-2.1.5" + +std::atomic CQnetITAP::keep_running(true); + +CQnetITAP::CQnetITAP(int mod) +: assigned_module(mod) +{ +} + +CQnetITAP::~CQnetITAP() +{ +} + +bool CQnetITAP::Initialize(const char *cfgfile) +{ + if (ReadConfig(cfgfile)) + return true; + + struct sigaction act; + act.sa_handler = &CQnetITAP::SignalCatch; + sigemptyset(&act.sa_mask); + if (sigaction(SIGTERM, &act, 0) != 0) { + printf("sigaction-TERM failed, error=%d\n", errno); + return true; + } + if (sigaction(SIGHUP, &act, 0) != 0) { + printf("sigaction-HUP failed, error=%d\n", errno); + return true; + } + if (sigaction(SIGINT, &act, 0) != 0) { + printf("sigaction-INT failed, error=%d\n", errno); + return true; + } + + Modem2Gate.SetUp(modem2gate.c_str()); + if (Gate2Modem.Open(gate2modem.c_str())) + return true; + + return false; +} + +int CQnetITAP::OpenITAP() +{ + int fd = open(ITAP_DEVICE.c_str(), O_RDWR | O_NOCTTY | O_NDELAY, 0); + if (fd < 0) { + printf("Failed to open device [%s], error=%d, message=%s\n", ITAP_DEVICE.c_str(), errno, strerror(errno)); + return -1; + } + + if (isatty(fd) == 0) { + printf("Device %s is not a tty device\n", ITAP_DEVICE.c_str()); + close(fd); + return -1; + } + + static termios t; + if (tcgetattr(fd, &t) < 0) { + printf("tcgetattr failed for %s, error=%d, message-%s\n", ITAP_DEVICE.c_str(), errno, strerror(errno)); + close(fd); + return -1; + } + + t.c_lflag &= ~(ECHO | ECHOE | ICANON | IEXTEN | ISIG); + t.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON | IXOFF | IXANY); + t.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS); + t.c_cflag |= CS8; + t.c_oflag &= ~(OPOST); + t.c_cc[VMIN] = 0; + t.c_cc[VTIME] = 10; + + cfsetospeed(&t, B38400); + cfsetispeed(&t, B38400); + + if (tcsetattr(fd, TCSANOW, &t) < 0) { + printf("tcsetattr failed for %s, error=%dm message=%s\n", ITAP_DEVICE.c_str(), errno, strerror(errno)); + close(fd); + return -1; + } + + return fd; +} + +void CQnetITAP::DumpSerialPacket(const char *title, const unsigned char *buf) +{ + printf("%s: ", title); + if (buf[0] > 41) { + printf("UNKNOWN: length=%u", (unsigned)buf[0]); + return; + } + SITAP itap; + memcpy(&itap, buf, buf[0]); + switch (itap.type) { + case 0x03U: //pong + printf("Pong\n"); + break; + case 0x10U: // header + case 0x20U: + printf("Header ur=%8.8s r1=%8.8s r2=%8.8s my=%8.8s/%4.4s", itap.header.ur, itap.header.r1, itap.header.r2, itap.header.my, itap.header.nm); + break; + case 0x12U: // data + case 0x22U: + printf("Data count=%u seq=%u f=%02u%02u%02u a=%02u%02u%02u%02u%02u%02u%02u%02u%02u t=%02u%02u%02u\n", itap.voice.counter, itap.voice.sequence, itap.header.flag[0], itap.header.flag[1], itap.header.flag[2], itap.voice.ambe[0], itap.voice.ambe[1], itap.voice.ambe[2], itap.voice.ambe[3], itap.voice.ambe[4], itap.voice.ambe[5], itap.voice.ambe[6], itap.voice.ambe[7], itap.voice.ambe[8], itap.voice.text[0], itap.voice.text[1], itap.voice.text[2]); + break; + case 0x11U: // header acknowledgement + case 0x21U: + printf("Header acknowledgement code=%02u", itap.header.flag[0]); + break; + case 0x13U: // data acknowledgment + case 0x23U: + printf("Data acknowledgement seq=%02u code=%02u", itap.header.flag[0], itap.header.flag[1]); + break; + default: + printf("UNKNOWN packet buf[0] = 0x%02u\n", buf[0]); + break; + } +} + +REPLY_TYPE CQnetITAP::GetITAPData(unsigned char *buf) +{ + // Some methods adapted from Jonathan G4KLX's CIcomController::GetResponse() + // and CSerialController::read() + + // Get the buffer size or nothing at all + int ret = read(serfd, buf, 1U); + if (ret < 0) { + printf("Error when reading first byte from the Icom radio %d: %s", errno, strerror(errno)); + return RT_ERROR; + } + + if (ret == 0) + return RT_TIMEOUT; + + if (buf[0U] == 0xFFU) + return RT_TIMEOUT; + + unsigned int length = buf[0U]; + + if (length >= 100U) { + printf("Invalid data received from the Icom radio, length=%d\n", length); + return RT_ERROR; + } + + unsigned int offset = 1U; + + while (offset < length) { + + ret = read(serfd, buf + offset, length - offset); + if (ret<0 && errno!=EAGAIN) { + printf("Error when reading buffer from the Icom radio %d: %s\n", errno, strerror(errno)); + return RT_ERROR; + } + + if (ret > 0) + offset += ret; + } + + switch (buf[1U]) { + case 0x03U: + return RT_PONG; + case 0x10U: + return RT_HEADER; + case 0x12U: + return RT_DATA; + case 0x21U: + return RT_HEADER_ACK; + case 0x23U: + return RT_DATA_ACK; + default: + return RT_UNKNOWN; + } +} + +void CQnetITAP::Run(const char *cfgfile) +{ + if (Initialize(cfgfile)) + return; + + serfd = OpenITAP(); + if (serfd < 0) + return; + + int ug2m = Gate2Modem.GetFD(); + printf("gate2modem=%d, serial=%d\n", ug2m, serfd); + + keep_running = true; + unsigned int poll_counter = 0; + bool is_alive = false; + acknowledged = true; + CTimer ackTimer; + CTimer lastdataTimer; + CTimer pingTimer; + double pingtime = 0.001; + + while (keep_running) { + + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(serfd, &readfds); + FD_SET(ug2m, &readfds); + int maxfs = (serfd > ug2m) ? serfd : ug2m; + + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 5000; + + // don't care about writefds and exceptfds: + // and we'll wait for 5 ms + int ret = select(maxfs+1, &readfds, NULL, NULL, &tv); + if (ret < 0) { + printf("ERROR: Run: select returned err=%d, %s\n", errno, strerror(errno)); + break; + } + + // check for a dead or disconnected radio + if (10.0 < lastdataTimer.time()) { + printf("no activity from radio for 10 sec. Exiting...\n"); + break; + } + + if (pingTimer.time() >= pingtime) { + if (poll_counter < 18 ) { + const unsigned char poll[2] = { 0xffu, 0xffu }; + SendTo(poll); + if (poll_counter == 17) + pingtime = 1.0; + } else { + const unsigned char ping[2] = { 0x02u, 0x02u }; + SendTo(ping); + } + poll_counter++; + pingTimer.start(); + } + + unsigned char buf[100]; + + if (keep_running && FD_ISSET(serfd, &readfds)) { // there is something to read! + switch (GetITAPData(buf)) { + case RT_ERROR: + keep_running = false; + break; + case RT_HEADER: + { + unsigned char ack_header[3] = { 0x03U, 0x11U, 0x0U }; + SendTo(ack_header); + } + if (ProcessITAP(buf)) + keep_running = false; + lastdataTimer.start(); + break; + case RT_DATA: + { + unsigned char ack_voice[4] = { 0x04U, 0x13U, 0x0U, 0x0U }; + ack_voice[2] = buf[2]; + SendTo(ack_voice); + } + if (ProcessITAP(buf)) + keep_running = false; + lastdataTimer.start(); + break; + case RT_PONG: + if (! is_alive) { + if (LOG_DEBUG) { + auto count = queue.size(); + if (count) + printf("%u packets in queue. Icom radio is connected.", (unsigned int)count); + } else + printf("Icom Radio is connected.\n"); + is_alive = true; + } + lastdataTimer.start(); + break; + case RT_HEADER_ACK: + if (acknowledged) { + fprintf(stderr, "ERROR: Header already acknowledged!\n"); + } else { + if (0x0U == buf[2]) + acknowledged = true; + } + lastdataTimer.start(); + break; + case RT_DATA_ACK: + if (acknowledged) { + fprintf(stderr, "ERROR: voice frame %d already acknowledged!\n", (int)buf[2]); + } else { + if (0x0U == buf[3]) + acknowledged = true; + } + lastdataTimer.start(); + break; + case RT_TIMEOUT: // nothing or 0xff + break; + default: + if (buf[0] != 255) + DumpSerialPacket("GetITAPData returned", buf); + break; + } + FD_CLR(serfd, &readfds); + } + + if (keep_running && FD_ISSET(ug2m, &readfds)) { + ssize_t len = Gate2Modem.Read(buf, 100); + + if (len < 0) { + printf("ERROR: Run: recvfrom(gsock) returned error %d, %s\n", errno, strerror(errno)); + break; + } + + if (0 == memcmp(buf, "DSVT", 4)) { + //printf("read %d bytes from QnetGateway\n", (int)len); + if (ProcessGateway(len, buf)) + break; + } + FD_CLR(ug2m, &readfds); + } + + // send queued frames + if (keep_running) { + if (acknowledged) { + if (is_alive) { + if (! queue.empty()) { + CFrame frame = queue.front(); + queue.pop(); + SendTo(frame.data()); + ackTimer.start(); + acknowledged = false; + } + } + } else { // we are waiting on an acknowledgement + if (ackTimer.time() >= 0.06) { + if (LOG_DEBUG) + fprintf(stderr, "Serial port communication error, restarting...\n"); + close(serfd); + poll_counter = 0; + pingtime = 0.001; + is_alive = false; + acknowledged = true; + lastdataTimer.start(); + pingTimer.start(); + serfd = OpenITAP(); + if (serfd < 0) { + keep_running = false; + } else { + while (! queue.empty()) + queue.pop(); + } + } + } + } + } + + close(serfd); + Gate2Modem.Close(); +} + +int CQnetITAP::SendTo(const unsigned char *buf) +{ + ssize_t n; + unsigned int ptr = 0; + unsigned int length = (0xffu == buf[0]) ? 2 : buf[0]; + + while (ptr < length) { + n = write(serfd, buf + ptr, length - ptr); + if (n < 0) { + if (EAGAIN != errno) { + printf("Error %d writing to dvap, message=%s\n", errno, strerror(errno)); + return -1; + } + } + ptr += n; + } + + n = 0; // send an ending 0xffu + while (0 == n) { + const unsigned char push = 0xffu; + n = write(serfd, &push, 1); + if (n < 0) { + if (EAGAIN != errno) { + printf("Error %d writing to dvap, message=%s\n", errno, strerror(errno)); + return -1; + } + } + } + + return length; +} + +bool CQnetITAP::ProcessGateway(const int len, const unsigned char *raw) +{ + static unsigned char counter = 0; + if (27==len || 56==len) { //here is dstar data + SDSVT dsvt; + memcpy(dsvt.title, raw, len); // transfer raw data to SDSVT struct + + SITAP itap; // destination + if (56 == len) { // write a Header packet + counter = 0; + itap.length = 41U; + itap.type = 0x20; + memcpy(itap.header.flag, dsvt.hdr.flag, 3); + if (RPTR_MOD == dsvt.hdr.rpt2[7]) { + memcpy(itap.header.r1, dsvt.hdr.rpt2, 8); + memcpy(itap.header.r2, dsvt.hdr.rpt1, 8); + } else { + memcpy(itap.header.r1, dsvt.hdr.rpt1, 8); + memcpy(itap.header.r2, dsvt.hdr.rpt2, 8); + } + memcpy(itap.header.ur, dsvt.hdr.urcall, 8); + memcpy(itap.header.my, dsvt.hdr.mycall, 8); + memcpy(itap.header.nm, dsvt.hdr.sfx, 4); + if (LOG_QSO) + printf("Queued ITAP to %s ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", ITAP_DEVICE.c_str(), itap.header.ur, itap.header.r1, itap.header.r2, itap.header.my, itap.header.nm); + } else { // write an AMBE packet + itap.length = 16U; + itap.type = 0x22U; + itap.voice.counter = counter++; + itap.voice.sequence = dsvt.ctrl; + if (LOG_QSO && (dsvt.ctrl & 0x40)) + printf("Queued ITAP end of stream\n"); + if ((dsvt.ctrl & ~0x40U) > 20) + printf("DEBUG: ProcessGateway: unexpected voice sequence number %d\n", itap.voice.sequence); + memcpy(itap.voice.ambe, dsvt.vasd.voice, 12); + } + queue.push(CFrame(&itap.length)); + + } else + printf("DEBUG: ProcessGateway: unusual packet size read len=%d\n", len); + return false; +} + +bool CQnetITAP::ProcessITAP(const unsigned char *buf) +{ + static short stream_id = 0U; + SITAP itap; + unsigned int len = (0x10U == buf[1]) ? 41 : 16; + memcpy(&itap.length, buf, len); // transfer raw data to SITAP struct + + // create a stream id if this is a header + if (41 == len) + stream_id = random.NewStreamID(); + + SDSVT dsvt; // destination + // sets most of the params + memcpy(dsvt.title, "DSVT", 4); + dsvt.config = (len==41) ? 0x10U : 0x20U; + memset(dsvt.flaga, 0U, 3U); + dsvt.id = 0x20; + dsvt.flagb[0] = 0x0; + dsvt.flagb[1] = 0x1; + dsvt.flagb[2] = ('B'==RPTR_MOD) ? 0x1 : (('C'==RPTR_MOD) ? 0x2 : 0x3); + dsvt.streamid = htons(stream_id); + + if (41 == len) { // header + dsvt.ctrl = 0x80; + + memcpy(dsvt.hdr.flag, itap.header.flag, 3); + + ////////////////// Terminal or Access ///////////////////////// + if (0 == memcmp(itap.header.r1, "DIRECT", 6)) { + // Terminal Mode! + memcpy(dsvt.hdr.rpt1, RPTR.c_str(), 7); // build r1 + dsvt.hdr.rpt1[7] = RPTR_MOD; // with module + memcpy(dsvt.hdr.rpt2, RPTR.c_str(), 7); // build r2 + dsvt.hdr.rpt2[7] = 'G'; // with gateway + if (' '==itap.header.ur[2] && ' '!=itap.header.ur[0]) { + // it's a command because it has as space in the 3rd position, we have to right-justify it! + // Terminal Mode left justifies short commands. + memset(dsvt.hdr.urcall, ' ', 8); // first file ur with spaces + if (' ' == itap.header.ur[1]) + dsvt.hdr.urcall[7] = itap.header.ur[0]; // one char command, like "E" or "I" + else + memcpy(dsvt.hdr.urcall+6, itap.header.ur, 2); // two char command, like "HX" or "S0" + } else + memcpy(dsvt.hdr.urcall, itap.header.ur, 8); // ur is at least 3 chars + } else { + // Access Point Mode + memcpy(dsvt.hdr.rpt1, itap.header.r1, 8); + memcpy(dsvt.hdr.rpt2, itap.header.r2, 8); + memcpy(dsvt.hdr.urcall, itap.header.ur, 8); + dsvt.hdr.flag[0] &= ~0x40U; // clear this bit + } + + memcpy(dsvt.hdr.mycall, itap.header.my, 8); + memcpy(dsvt.hdr.sfx, itap.header.nm, 4); + calcPFCS(dsvt.hdr.flag, dsvt.hdr.pfcs); + if (56 != Modem2Gate.Write(dsvt.title, 56)) { + printf("ERROR: ProcessITAP: Could not write gateway header packet\n"); + return true; + } + if (LOG_QSO) + printf("Sent DSVT to gateway, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", ntohs(dsvt.streamid), dsvt.hdr.urcall, dsvt.hdr.rpt1, dsvt.hdr.rpt2, dsvt.hdr.mycall, dsvt.hdr.sfx); + } else if (16 == len) { // ambe + dsvt.ctrl = itap.voice.sequence; + memcpy(dsvt.vasd.voice, itap.voice.ambe, 12); + if (27 != Modem2Gate.Write(dsvt.title, 27)) { + printf("ERROR: ProcessMMDVM: Could not write gateway voice packet\n"); + return true; + } + + if (LOG_QSO && (dsvt.ctrl & 0x40)) + printf("Sent dsvt end of streamid=%04x\n", ntohs(dsvt.streamid)); + } + + return false; +} + +// process configuration file and return true if there was a problem +bool CQnetITAP::ReadConfig(const char *cfgFile) +{ + CQnetConfigure cfg; + printf("Reading file %s\n", cfgFile); + if (cfg.Initialize(cfgFile)) + return true; + + const std::string estr; // an empty string + std::string type; + std::string itap_path("module_"); + if (0 > assigned_module) { + // we need to find the lone itap module + for (int i=0; i<3; i++) { + std::string test(itap_path); + test.append(1, 'a'+i); + if (cfg.KeyExists(test)) { + cfg.GetValue(test, estr, type, 1, 16); + if (type.compare("itap")) + continue; // this ain't it! + itap_path.assign(test); + assigned_module = i; + break; + } + } + if (0 > assigned_module) { + fprintf(stderr, "Error: no 'itap' module found\n!"); + return true; + } + } else { + // make sure itap module is defined + itap_path.append(1, 'a' + assigned_module); + if (cfg.KeyExists(itap_path)) { + cfg.GetValue(itap_path, estr, type, 1, 16); + if (type.compare("itap")) { + fprintf(stderr, "%s = %s is not 'itap' type!\n", itap_path.c_str(), type.c_str()); + return true; + } + } else { + fprintf(stderr, "Module '%c' is not defined.\n", 'a'+assigned_module); + return true; + } + } + RPTR_MOD = 'A' + assigned_module; + + cfg.GetValue(itap_path+"_device", type, ITAP_DEVICE, 7, FILENAME_MAX); + cfg.GetValue("gateway_gate2modem"+std::string(1, 'a'+assigned_module), estr, gate2modem, 1, FILENAME_MAX); + cfg.GetValue("gateway_modem2gate", estr, modem2gate, 1, FILENAME_MAX); + + itap_path.append("_callsign"); + if (cfg.KeyExists(itap_path)) { + if (cfg.GetValue(itap_path, type, RPTR, 3, 6)) + return true; + } else { + itap_path.assign("ircddb_login"); + if (cfg.KeyExists(itap_path)) { + if (cfg.GetValue(itap_path, estr, RPTR, 3, 6)) + return true; + } + } + int l = RPTR.length(); + if (l<3 || l>6) { + printf("Call '%s' is invalid length!\n", RPTR.c_str()); + return true; + } else { + for (int i=0; i> 8) ^ crc_tabccitt[tmp]; + } + crc_dstar_ffff = ~crc_dstar_ffff; + tmp = crc_dstar_ffff; + + pfcs[0] = (unsigned char)(crc_dstar_ffff & 0xff); + pfcs[1] = (unsigned char)((tmp >> 8) & 0xff); + + return; +} + +int main(int argc, const char **argv) +{ + setbuf(stdout, NULL); + if (2 != argc) { + fprintf(stderr, "usage: %s path_to_config_file\n", argv[0]); + return 1; + } + + if ('-' == argv[1][0]) { + printf("\nQnetITAP Version #%s Copyright (C) 2018-2019 by Thomas A. Early N7TAE\n", ITAP_VERSION); + printf("QnetITAP comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); + printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n"); + return 0; + } + + const char *qn = strstr(argv[0], "qnitap"); + if (NULL == qn) { + fprintf(stderr, "Error finding 'qnitap' in %s!\n", argv[0]); + return 1; + } + qn += 6; + + int assigned_module; + switch (*qn) { + case NULL: + assigned_module = -1; + break; + case 'a': + assigned_module = 0; + break; + case 'b': + assigned_module = 1; + break; + case 'c': + assigned_module = 2; + break; + default: + fprintf(stderr, "assigned module must be a, b or c\n"); + return 1; + } + + CQnetITAP qnitap(assigned_module); + + qnitap.Run(argv[1]); + + printf("%s is closing.\n", argv[0]); + + return 0; +} diff --git a/QnetITAP.h b/QnetITAP.h new file mode 100644 index 0000000..ee05743 --- /dev/null +++ b/QnetITAP.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2018-2019 by Thomas A. Early N7TAE + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include "Random.h" // for streamid generation +#include "UnixDgramSocket.h" + +#define CALL_SIZE 8 +#define IP_SIZE 15 + +enum REPLY_TYPE { + RT_TIMEOUT, + RT_ERROR, + RT_UNKNOWN, + RT_HEADER, + RT_DATA, + RT_HEADER_ACK, + RT_DATA_ACK, + RT_PONG +}; + +// Icom Terminal and Access Point Mode data structure +#pragma pack(push, 1) +typedef struct itap_tag { + unsigned char length; + // 41 for header + // 16 for voice + unsigned char type; + // 0x03U pong + // 0x10U header from icom + // 0x11U acknowledgment + // 0x12U data from icom (it's EOT if voice.sequence bit 0x40 is set) + // 0x13U acknowledgment + // 0x20U header to icom + // 0x21U header acknowledgment + // 0x22U data to icom + // 0x23U data acknowledgment + union { + struct { + unsigned char flag[3]; + unsigned char r2[8]; + unsigned char r1[8]; + unsigned char ur[8]; + unsigned char my[8]; + unsigned char nm[4]; + } header; + struct { + unsigned char counter; // ordinal counter is reset with each header + unsigned char sequence; // is modulo 21 + unsigned char ambe[9]; + unsigned char text[3]; + } voice; + }; +} SITAP; +#pragma pack(pop) + +class CFrame +{ +public: + CFrame(const unsigned char *buf) { + memcpy(&frame.length, buf, buf[0]); + } + + CFrame(const CFrame &from) { + memcpy(&frame.length, from.data(), from.size()); + } + + ~CFrame() {} + + size_t size() const { return (size_t)frame.length; } + + const unsigned char *data() const { return &frame.length; } + +private: + SITAP frame; +}; + +class CQnetITAP +{ +public: + // functions + CQnetITAP(int mod); + ~CQnetITAP(); + void Run(const char *cfgfile); + + // data + static std::atomic keep_running; + +private: + int assigned_module; + // functions + bool Initialize(const char *cfgfile); + static void SignalCatch(const int signum); + bool ProcessGateway(const int len, const unsigned char *raw); + bool ProcessITAP(const unsigned char *raw); + int OpenITAP(); + int SendTo(const unsigned char *buf); + REPLY_TYPE GetITAPData(unsigned char *buf); + void calcPFCS(const unsigned char *packet, unsigned char *pfcs); + void DumpSerialPacket(const char *title, const unsigned char *); + + // read configuration file + bool ReadConfig(const char *); + + // config data + char RPTR_MOD; + std::string ITAP_DEVICE, RPTR; + bool LOG_QSO, LOG_DEBUG; + + // parameters + int serfd; + unsigned char tapcounter; + + // helpers + CRandom random; + + // unix sockets + std::string modem2gate, gate2modem; + CUnixDgramWriter Modem2Gate; + CUnixDgramReader Gate2Modem; + + // Queue + std::queue queue; + bool acknowledged; +}; diff --git a/QnetLink.cpp b/QnetLink.cpp index 690dacf..2d9c2ac 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -1,7 +1,7 @@ /* * Copyright (C) 2010 by Scott Lawson KI4LKF - * Copyright (C) 2015,2018 by Thomas A. Early N7TAE + * Copyright (C) 2015,2018,2019 by Thomas A. Early N7TAE * * 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 @@ -37,185 +37,38 @@ #include #include -#include #include #include +#include +#include #include #include -#include -/* Required for Binary search trees using C++ STL */ -#include -#include -#include #include #include #include -#include -#include "versions.h" -using namespace libconfig; - -/*** version number must be x.xx ***/ -#define VERSION LINK_VERSION -#define CALL_SIZE 8 -#define IP_SIZE 15 -#define QUERY_SIZE 56 -#define MAXHOSTNAMELEN 64 -#define TIMEOUT 50 - -/* configuration data */ -static std::string login_call; -static std::string owner; -static std::string to_g2_external_ip; -static std::string my_g2_link_ip; -static std::string gwys; -static std::string status_file; -static std::string announce_dir; -static bool qso_details; -static bool bool_rptr_ack; -static bool announce; -static int rmt_xrf_port; -static int rmt_ref_port; -static int rmt_dcs_port; -static int my_g2_link_port; -static int to_g2_external_port; -static int delay_between; -static int delay_before; -static char link_at_startup[CALL_SIZE+1]; -static unsigned int max_dongles; -static unsigned int saved_max_dongles; -static long rf_inactivity_timer[3]; - -static unsigned char REF_ACK[3] = { 3, 96, 0 }; - -// This is the data payload in the map: inbound_list -// This is for inbound dongles - -struct inbound { - char call[CALL_SIZE + 1]; // the callsign of the remote - struct sockaddr_in sin; // IP and port of remote - short countdown; // if countdown expires, the connection is terminated - char mod; // A B C This user talked on this module - char client; // dvap, dvdongle -}; - -// the Key in this inbound_list map is the unique IP address of the remote -static std::map inbound_list; -static std::set admin; -static std::set link_unlink_user; -static std::set link_blacklist; - -#define LH_MAX_SIZE 39 -typedef std::map dt_lh_type; -static dt_lh_type dt_lh_list; - -static struct { - char to_call[CALL_SIZE + 1]; - struct sockaddr_in toDst4; - char from_mod; - char to_mod; - short countdown; - bool is_connected; - unsigned char in_streamid[2]; // incoming from remote systems - unsigned char out_streamid[2]; // outgoing to remote systems -} to_remote_g2[3]; - -// broadcast for data arriving from xrf to local rptr -static struct { - unsigned char xrf_streamid[2]; // streamid from xrf - unsigned char rptr_streamid[2][2]; // generated streamid to rptr(s) -} brd_from_xrf; -static unsigned char from_xrf_torptr_brd[56]; -static short brd_from_xrf_idx = 0; - -// broadcast for data arriving from local rptr to xrf -static struct { - unsigned char from_rptr_streamid[2]; - unsigned char to_rptr_streamid[2][2]; -} brd_from_rptr; -static unsigned char fromrptr_torptr_brd[56]; -static short brd_from_rptr_idx = 0; - -static struct { - unsigned char streamid[2]; - time_t last_time; // last time RF user talked -} tracing[3] = { - { {0,0}, 0 }, - { {0,0}, 0 }, - { {0,0}, 0 } -}; - -// input from remote -static int xrf_g2_sock = -1; -static int ref_g2_sock = -1; -static int dcs_g2_sock = -1; -static struct sockaddr_in fromDst4; - -// After we receive it from remote g2, -// we must feed it to our local repeater. -static struct sockaddr_in toLocalg2; - -// input from our own local repeater -static int rptr_sock = -1; -static struct sockaddr_in fromRptr; - -static fd_set fdset; -static struct timeval tv; - -static std::atomic keep_running(true); - -// Used to validate incoming donglers -static regex_t preg; - -const char* G2_html = "" - "" - "" - "
" - "REPEATER QnetGateway v1.0+" - "
"; - -// the map of remotes -// key is the callsign, data is the host -typedef std::map gwy_list_type; -static gwy_list_type gwy_list; - -static unsigned char queryCommand[QUERY_SIZE]; - -// START: TEXT crap -static char dtmf_mycall[3][CALL_SIZE + 1] = { {""}, {""}, {""} }; -static bool new_group[3] = { true, true, true }; -static int header_type = 0; -static bool GPS_seen[3] = { false, false, false }; -unsigned char tmp_txt[3]; -static char *p_tmp2 = NULL; -// END: TEXT crap - -// this is used for the "dashboard and qso_details" to avoid processing multiple headers -static struct { - unsigned char sid[2]; -} old_sid[3] = { - { {0x00, 0x00} }, - { {0x00, 0x00} }, - { {0x00, 0x00} } -}; - -static bool load_gwys(const std::string &filename); -static void calcPFCS(unsigned char *packet, int len); -static bool read_config(char *); -static bool srv_open(); -static void srv_close(); -static void sigCatch(int signum); -static void g2link(char from_mod, char *call, char to_mod); -static void runit(); -static void print_status_file(); -static void send_heartbeat(); -static bool resolve_rmt(char *name, int type, struct sockaddr_in *addr); -static void audio_notify(char *notify_msg); -static void rptr_ack(short i); -static void AudioNotifyThread(char *arg); -static void RptrAckThread(char *arg); - -static bool resolve_rmt(char *name, int type, struct sockaddr_in *addr) + +#include "DPlusAuthenticator.h" +#include "QnetConfigure.h" +#include "QnetLink.h" + +#define LINK_VERSION "QnetLink-7.2" + +std::atomic CQnetLink::keep_running(true); + +CQnetLink::CQnetLink() +{ + memset(tracing, 0, 3 * sizeof(struct tracing_tag)); + memset(dtmf_mycall, 0, 3 * (CALL_SIZE+1)); + memset(old_sid, 0, 6); +} + +CQnetLink::~CQnetLink() +{ + speak.clear(); +} + +bool CQnetLink::resolve_rmt(const char *name, const unsigned short port, CSockAddress &addr) { struct addrinfo hints; struct addrinfo *res; @@ -223,8 +76,8 @@ static bool resolve_rmt(char *name, int type, struct sockaddr_in *addr) bool found = false; memset(&hints, 0x00, sizeof(struct addrinfo)); - hints.ai_family = AF_INET; - hints.ai_socktype = type; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; int rc = getaddrinfo(name, NULL, &hints, &res); if (rc != 0) { @@ -232,34 +85,51 @@ static bool resolve_rmt(char *name, int type, struct sockaddr_in *addr) return false; } - for (rp = res; rp != NULL; rp = rp->ai_next) { - if ((rp->ai_family == AF_INET) && - (rp->ai_socktype == type)) { - memcpy(addr, rp->ai_addr, sizeof(struct sockaddr_in)); - found = true; - break; + for (rp=res; rp!=NULL; rp=rp->ai_next) { + if ((AF_INET==rp->ai_family || AF_INET6==rp->ai_family) && SOCK_DGRAM==rp->ai_socktype) { + if (AF_INET == rp->ai_family) { + char saddr[INET_ADDRSTRLEN]; + struct sockaddr_in *addr4 = (struct sockaddr_in *)rp->ai_addr; + if (inet_ntop(rp->ai_family, &addr4->sin_addr, saddr, INET_ADDRSTRLEN)) { + addr.Initialize(rp->ai_family, port, saddr); + found = true; + break; + } + } else if (AF_INET6 == rp->ai_family) { + char saddr[INET6_ADDRSTRLEN]; + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)rp->ai_addr; + if (inet_ntop(rp->ai_family, &addr6->sin6_addr, saddr, INET6_ADDRSTRLEN)) { + addr.Initialize(rp->ai_family, port, saddr); + found = true; + break; + } + } } } freeaddrinfo(res); + + if (found && strcmp(name, addr.GetAddress())) { + printf("Node address %s on port %u resolved to %s\n", name, port, addr.GetAddress()); + } + return found; } /* send keepalive to donglers */ -static void send_heartbeat() +void CQnetLink::send_heartbeat() { - inbound *inbound_ptr; bool removed = false; for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; - sendto(ref_g2_sock, REF_ACK, 3, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + SINBOUND *inbound = (SINBOUND *)pos->second; + sendto(ref_g2_sock, REF_ACK, 3, 0, inbound->addr.GetPointer(), inbound->addr.GetSize()); - if (inbound_ptr->countdown >= 0) - inbound_ptr->countdown --; + if (inbound->countdown >= 0) + inbound->countdown --; - if (inbound_ptr->countdown < 0) { + if (inbound->countdown < 0) { removed = true; - printf("call=%s timeout, removing %s, users=%d\n", inbound_ptr->call, pos->first.c_str(), (int)inbound_list.size() - 1); + printf("call=%s timeout, removing %s, users=%d\n", inbound->call, pos->first.c_str(), (int)inbound_list.size() - 1); free(pos->second); pos->second = NULL; @@ -270,7 +140,7 @@ static void send_heartbeat() print_status_file(); } -static void rptr_ack(short i) +void CQnetLink::rptr_ack(short i) { static char mod_and_RADIO_ID[3][22]; @@ -286,31 +156,28 @@ static void rptr_ack(short i) if (to_remote_g2[i].is_connected) { memcpy(mod_and_RADIO_ID[i] + 1, "LINKED TO ", 10); - memcpy(mod_and_RADIO_ID[i] + 11, to_remote_g2[i].to_call, CALL_SIZE); + memcpy(mod_and_RADIO_ID[i] + 11, to_remote_g2[i].cs, CALL_SIZE); mod_and_RADIO_ID[i][11 + CALL_SIZE] = to_remote_g2[i].to_mod; - } else if (to_remote_g2[i].to_call[0] != '\0') { + } else if (to_remote_g2[i].cs[0] != '\0') { memcpy(mod_and_RADIO_ID[i] + 1, "TRYING ", 10); - memcpy(mod_and_RADIO_ID[i] + 11, to_remote_g2[i].to_call, CALL_SIZE); + memcpy(mod_and_RADIO_ID[i] + 11, to_remote_g2[i].cs, CALL_SIZE); mod_and_RADIO_ID[i][11 + CALL_SIZE] = to_remote_g2[i].to_mod; } else { memcpy(mod_and_RADIO_ID[i] + 1, "NOT LINKED", 10); } try { - std::async(std::launch::async, RptrAckThread, mod_and_RADIO_ID[i]); + std::async(std::launch::async, &CQnetLink::RptrAckThread, this, mod_and_RADIO_ID[i]); } catch (const std::exception &e) { printf("Failed to start RptrAckThread(). Exception: %s\n", e.what()); } return; } -static void RptrAckThread(char *arg) +void CQnetLink::RptrAckThread(char *arg) { char from_mod = arg[0]; char RADIO_ID[21]; memcpy(RADIO_ID, arg + 1, 21); - unsigned char buf[56]; - unsigned int aseed; - time_t tnow = 0; unsigned char silence[12] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8, 0x16, 0x29, 0xf5 }; struct sigaction act; @@ -326,116 +193,110 @@ static void RptrAckThread(char *arg) return; } - time(&tnow); - aseed = tnow + pthread_self(); - - u_int16_t streamid_raw = (::rand_r(&aseed) % 65535U) + 1U; + short int streamid_raw = Random.NewStreamID(); sleep(delay_before); printf("sending ACK+text, mod:[%c], RADIO_ID=[%s]\n", from_mod, RADIO_ID); - memcpy(buf,"DSVT", 4); - buf[4] = 0x10; - buf[5] = 0x00; - buf[6] = 0x00; - buf[7] = 0x00; + SDSVT dsvt; - buf[8] = 0x20; - buf[9] = 0x00; - buf[10] = 0x01; - buf[11] = 0x00; + memcpy(dsvt.title, "DSVT", 4); + dsvt.config = 0x10; + dsvt.flaga[0] = dsvt.flaga[1] = dsvt.flaga[2] = 0x0; - buf[12] = streamid_raw / 256U; - buf[13] = streamid_raw % 256U; - buf[14] = 0x80; - buf[15] = 0x01; /* we do not want to set this to 0x01 */ - buf[16] = 0x00; - buf[17] = 0x00; + dsvt.id = 0x20; + dsvt.flagb[0] =dsvt.flagb[2] = 0x0; + dsvt.flagb[1] = 0x1; - memcpy(buf + 18, owner.c_str(), CALL_SIZE); - buf[25] = from_mod; + dsvt.streamid = htons(streamid_raw); + dsvt.ctrl = 0x80; + dsvt.hdr.flag[0] = 0x1; + dsvt.hdr.flag[1] = dsvt.hdr.flag[2] = 0x0; - memcpy(buf + 26, owner.c_str(), CALL_SIZE); - buf[33] = 'G'; + memcpy(dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE); + dsvt.hdr.rpt1[7] = from_mod; - memcpy(buf + 34, "CQCQCQ ", CALL_SIZE); + memcpy(dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE); + dsvt.hdr.rpt2[7] = 'G'; - memcpy(buf + 42, owner.c_str(), CALL_SIZE); - buf[49] = from_mod; + memcpy(dsvt.hdr.urcall, "CQCQCQ ", CALL_SIZE); - memcpy(buf + 50, "RPTR", 4); - calcPFCS(buf,56); - sendto(rptr_sock, buf, 56, 0, (struct sockaddr *)&toLocalg2, sizeof(toLocalg2)); - std::this_thread::sleep_for(std::chrono::milliseconds(delay_between)); + memcpy(dsvt.hdr.mycall, owner.c_str(), CALL_SIZE); + dsvt.hdr.mycall[7] = from_mod; - buf[4] = 0x20; - memcpy(buf + 15, silence, 9); + memcpy(dsvt.hdr.sfx, "RPTR", 4); + calcPFCS(dsvt.title,56); + Link2Gate.Write(dsvt.title, 56); + //std::this_thread::sleep_for(std::chrono::milliseconds(delay_between)) + + dsvt.config = 0x20; + memcpy(dsvt.vasd.voice, silence, 9); /* start sending silence + announcement text */ for (int i=0; i<10; i++) { - buf[14] = (unsigned char)i; + dsvt.ctrl = (unsigned char)i; switch (i) { case 0: - buf[24] = 0x55; - buf[25] = 0x2d; - buf[26] = 0x16; + dsvt.vasd.text[0] = 0x55; + dsvt.vasd.text[1] = 0x2d; + dsvt.vasd.text[2] = 0x16; break; case 1: - buf[24] = '@' ^ 0x70; - buf[25] = RADIO_ID[0] ^ 0x4f; - buf[26] = RADIO_ID[1] ^ 0x93; + dsvt.vasd.text[0] = '@' ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[0] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[1] ^ 0x93; break; case 2: - buf[24] = RADIO_ID[2] ^ 0x70; - buf[25] = RADIO_ID[3] ^ 0x4f; - buf[26] = RADIO_ID[4] ^ 0x93; + dsvt.vasd.text[0] = RADIO_ID[2] ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[3] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[4] ^ 0x93; break; case 3: - buf[24] = 'A' ^ 0x70; - buf[25] = RADIO_ID[5] ^ 0x4f; - buf[26] = RADIO_ID[6] ^ 0x93; + dsvt.vasd.text[0] = 'A' ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[5] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[6] ^ 0x93; break; case 4: - buf[24] = RADIO_ID[7] ^ 0x70; - buf[25] = RADIO_ID[8] ^ 0x4f; - buf[26] = RADIO_ID[9] ^ 0x93; + dsvt.vasd.text[0] = RADIO_ID[7] ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[8] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[9] ^ 0x93; break; case 5: - buf[24] = 'B' ^ 0x70; - buf[25] = RADIO_ID[10] ^ 0x4f; - buf[26] = RADIO_ID[11] ^ 0x93; + dsvt.vasd.text[0] = 'B' ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[10] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[11] ^ 0x93; break; case 6: - buf[24] = RADIO_ID[12] ^ 0x70; - buf[25] = RADIO_ID[13] ^ 0x4f; - buf[26] = RADIO_ID[14] ^ 0x93; + dsvt.vasd.text[0] = RADIO_ID[12] ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[13] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[14] ^ 0x93; break; case 7: - buf[24] = 'C' ^ 0x70; - buf[25] = RADIO_ID[15] ^ 0x4f; - buf[26] = RADIO_ID[16] ^ 0x93; + dsvt.vasd.text[0] = 'C' ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[15] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[16] ^ 0x93; break; case 8: - buf[24] = RADIO_ID[17] ^ 0x70; - buf[25] = RADIO_ID[18] ^ 0x4f; - buf[26] = RADIO_ID[19] ^ 0x93; + dsvt.vasd.text[0] = RADIO_ID[17] ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[18] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[19] ^ 0x93; break; case 9: - buf[14] |= 0x40; - buf[24] = 0x16; - buf[25] = 0x29; - buf[26] = 0xf5; + dsvt.ctrl |= 0x40; + dsvt.vasd.text[0] = 0x16; + dsvt.vasd.text[1] = 0x29; + dsvt.vasd.text[2] = 0xf5; break; } - sendto(rptr_sock, buf, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(toLocalg2)); + Link2Gate.Write(dsvt.title, 27); if (i < 9) std::this_thread::sleep_for(std::chrono::milliseconds(delay_between)); } } -static void print_status_file() +void CQnetLink::print_status_file() { FILE *statusfp = fopen(status_file.c_str(), "w"); if (!statusfp) @@ -450,24 +311,39 @@ static void print_status_file() /* print connected donglers */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound *inbound_ptr = (inbound *)pos->second; - fprintf(statusfp, fstr, 'p', inbound_ptr->call, 'p', pos->first.c_str(), tm1.tm_mon+1,tm1.tm_mday,tm1.tm_year % 100, tm1.tm_hour,tm1.tm_min,tm1.tm_sec); + SINBOUND *inbound = (SINBOUND *)pos->second; + fprintf(statusfp, fstr, 'p', inbound->call, 'p', pos->first.c_str(), tm1.tm_mon+1,tm1.tm_mday,tm1.tm_year % 100, tm1.tm_hour,tm1.tm_min,tm1.tm_sec); } /* print linked repeaters-reflectors */ + SLINKFAMILY fam; + memcpy(fam.title, "LINK", 4); for (int i=0; i<3;i++) { if (to_remote_g2[i].is_connected) { - fprintf(statusfp, fstr, to_remote_g2[i].from_mod, to_remote_g2[i].to_call, to_remote_g2[i].to_mod, inet_ntoa(to_remote_g2[i].toDst4.sin_addr), - tm1.tm_mon+1, tm1.tm_mday ,tm1.tm_year % 100, tm1.tm_hour, tm1.tm_min, tm1.tm_sec); - } - } + fprintf(statusfp, fstr, to_remote_g2[i].from_mod, to_remote_g2[i].cs, to_remote_g2[i].to_mod, to_remote_g2[i].addr.GetAddress(), tm1.tm_mon+1, tm1.tm_mday ,tm1.tm_year % 100, tm1.tm_hour, tm1.tm_min, tm1.tm_sec); + // also inform gateway + fam.family[i] = to_remote_g2[i].addr.GetFamily(); + } else { + fam.family[i] = AF_UNSPEC; + } + } + Link2Gate.Write(fam.title, sizeof(SLINKFAMILY)); fclose(statusfp); } } /* Open text file of repeaters, reflectors */ -static bool load_gwys(const std::string &filename) +bool CQnetLink::load_gwys(const std::string &filename) { + // DPlus Authenticate + if (dplus_authorize && !dplus_priority) { + CDPlusAuthenticator auth(login_call, std::string("auth.dstargateway.org")); + if (auth.Process(gwy_list, dplus_reflectors, dplus_repeaters)) + fprintf(stdout, "DPlus Authorization failed.\n"); + else + fprintf(stderr, "DPlus Authorization complete!\n"); + } + char inbuf[1024]; const char *delim = " "; @@ -479,9 +355,6 @@ static bool load_gwys(const std::string &filename) char payload[MAXHOSTNAMELEN + 1 + 5 + 1]; unsigned short j; - gwy_list_type::iterator gwy_pos; - std::pair gwy_insert_pair; - printf("Trying to open file %s\n", filename.c_str()); FILE *fp = fopen(filename.c_str(), "r"); if (fp == NULL) { @@ -552,24 +425,29 @@ static bool load_gwys(const std::string &filename) /* copy the payload(host port) */ sprintf(payload, "%s %s", host, port); - gwy_pos = gwy_list.find(call); - if (gwy_pos == gwy_list.end()) { - gwy_insert_pair = gwy_list.insert(std::pair(call,payload)); - if (gwy_insert_pair.second) - printf("Added Call=[%s], payload=[%s]\n",call, payload); - else - printf("Failed to add: Call=[%s], payload=[%s]\n",call, payload); - } else - printf("Call [%s] is duplicate\n", call); + auto gwy_pos = gwy_list.find(call); + if (gwy_pos != gwy_list.end()) + printf("%s %s has been redefined!\n", call, payload); + gwy_list[call] = payload; } fclose(fp); + // DPlus Authenticate + if (dplus_authorize && dplus_priority) { + CDPlusAuthenticator auth(login_call, std::string("auth.dstargateway.org")); + if (auth.Process(gwy_list, dplus_reflectors, dplus_repeaters)) + fprintf(stdout, "DPlus Authorization failed.\n"); + else + fprintf(stderr, "DPlus Authorization completed!\n"); + } - printf("Added %d gateways\n", (int)gwy_list.size()); + //for (auto it=gwy_list.begin(); it!=gwy_list.end(); it++) + // printf("%s %s\n", it->first.c_str(), it->second.c_str()); + printf("Added %d gateways from gwys.txt\n", (int)gwy_list.size()); return true; } /* compute checksum */ -static void calcPFCS(unsigned char *packet, int len) +void CQnetLink::calcPFCS(unsigned char *packet, int len) { unsigned short crc_tabccitt[256] = { 0x0000,0x1189,0x2312,0x329b,0x4624,0x57ad,0x6536,0x74bf,0x8c48,0x9dc1,0xaf5a,0xbed3,0xca6c,0xdbe5,0xe97e,0xf8f7, @@ -621,261 +499,151 @@ static void calcPFCS(unsigned char *packet, int len) return; } -bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%u]\n", path, value); - return true; -} - -bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value) +void CQnetLink::ToUpper(std::string &s) { - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%lg]\n", path, value); - return true; + for (auto it=s.begin(); it!=s.end(); it++) + if (islower(*it)) + *it = toupper(*it); } -bool get_value(const Config &cfg, const char *path, bool &value, bool default_value) +void CQnetLink::UnpackCallsigns(const std::string &str, std::set &set, const std::string &delimiters) { - if (! cfg.lookupValue(path, value)) - value = default_value; - printf("%s = [%s]\n", path, value ? "true" : "false"); - return true; + std::string::size_type lastPos = str.find_first_not_of(delimiters, 0); // Skip delimiters at beginning. + std::string::size_type pos = str.find_first_of(delimiters, lastPos); // Find first non-delimiter. + + while (std::string::npos != pos || std::string::npos != lastPos) { + std::string element = str.substr(lastPos, pos-lastPos); + if (element.length()>=3 && element.length()<=6) { + ToUpper(element); + element.resize(CALL_SIZE, ' '); + set.insert(element); // Found a token, add it to the list. + } else + fprintf(stderr, "found bad callsign in list: %s\n", str.c_str()); + lastPos = str.find_first_not_of(delimiters, pos); // Skip delimiters. + pos = str.find_first_of(delimiters, lastPos); // Find next non-delimiter. + } } -bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) +void CQnetLink::PrintCallsigns(const std::string &key, const std::set &set) { - if (cfg.lookupValue(path, value)) { - int l = value.length(); - if (lmax) { - printf("%s='%s' is has to be between %d and %d characters\n", path, value.c_str(), min, max); - return false; - } - } else - value = default_value; - printf("%s = [%s]\n", path, value.c_str()); - return true; + printf("%s = [", key.c_str()); + for (auto it=set.begin(); it!=set.end(); it++) { + if (it != set.begin()) + printf(","); + printf("%s", (*it).c_str()); + } + printf("]\n"); } /* process configuration file */ -static bool read_config(char *cfgFile) +bool CQnetLink::read_config(const char *cfgFile) { - unsigned short i; - Config cfg; + CQnetConfigure cfg; + const std::string estr; // an empty string printf("Reading file %s\n", cfgFile); - // Read the file. If there is an error, report it and exit. - try { - cfg.readFile(cfgFile); - } - catch(const FileIOException &fioex) { - printf("Can't read %s\n", cfgFile); - return false; - } - catch(const ParseException &pex) { - printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); - return false; - } - - std::string value; - std::string key = "link.ref_login"; - if (cfg.lookupValue(key, login_call) || cfg.lookupValue("ircddb.login", login_call)) { - int l = login_call.length(); - if (l<3 || l>CALL_SIZE-2) { - printf("Call '%s' is invalid length!\n", login_call.c_str()); - return false; - } else { - for (i=0; i2 && l<=CALL_SIZE-2) { - for (unsigned int j=0; j2 && l2 && l2 && l<=CALL_SIZE-2) { - for (i=0; iREPEATER QnetGateway v1.0+"); - printf("sending link request from mod %c to link with: [%s] mod %c [%s]\n", to_remote_g2[i].from_mod, to_remote_g2[i].to_call, to_remote_g2[i].to_mod, payload); - sendto(dcs_g2_sock, link_request, 519, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); + printf("sending link request from mod %c to link with: [%s] mod %c [%s]\n", to_remote_g2[i].from_mod, to_remote_g2[i].cs, to_remote_g2[i].to_mod, payload); + sendto(dcs_g2_sock, link_request, 519, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); } else if (port_i == rmt_ref_port) { + int counter; for (counter = 0; counter < 3; counter++) { if (counter != i) { - if ( (to_remote_g2[counter].to_call[0] != '\0') && - (strcmp(to_remote_g2[counter].to_call,to_remote_g2[i].to_call) == 0) ) + if ( (to_remote_g2[counter].cs[0] != '\0') && (strcmp(to_remote_g2[counter].cs, to_remote_g2[i].cs) == 0) ) break; } } if (counter > 2) { - printf("sending link command from mod %c to: [%s] mod %c [%s]\n", to_remote_g2[i].from_mod, to_remote_g2[i].to_call, to_remote_g2[i].to_mod, payload); + printf("sending link command from mod %c to: [%s] mod %c [%s]\n", to_remote_g2[i].from_mod, to_remote_g2[i].cs, to_remote_g2[i].to_mod, payload); queryCommand[0] = 5; queryCommand[1] = 0; @@ -1153,7 +900,7 @@ static void g2link(char from_mod, char *call, char to_mod) queryCommand[3] = 0; queryCommand[4] = 1; - sendto(ref_g2_sock, queryCommand, 5, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); + sendto(ref_g2_sock, queryCommand, 5, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); } else { if (to_remote_g2[counter].is_connected) { to_remote_g2[i].is_connected = true; @@ -1163,14 +910,13 @@ static void g2link(char from_mod, char *call, char to_mod) tracing[i].last_time = time(NULL); // announce it here - strcpy(linked_remote_system, to_remote_g2[i].to_call); + strcpy(linked_remote_system, to_remote_g2[i].cs); space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); } else - printf("status from %s %c pending\n", to_remote_g2[i].to_call, to_remote_g2[i].to_mod); + printf("status from %s %c pending\n", to_remote_g2[i].cs, to_remote_g2[i].to_mod); } } } @@ -1178,7 +924,7 @@ static void g2link(char from_mod, char *call, char to_mod) } /* signal catching function */ -static void sigCatch(int signum) +void CQnetLink::sigCatch(int signum) { /* do NOT do any serious work here */ if ((signum == SIGTERM) || (signum == SIGINT)) @@ -1186,19 +932,12 @@ static void sigCatch(int signum) return; } -static void runit() +void CQnetLink::Process() { - socklen_t fromlen; - int recvlen; - int recvlen2; - short i,j,k; - char temp_repeater[CALL_SIZE + 1]; time_t tnow = 0, hb = 0; - int rc = 0; char *p = NULL; - char notify_msg[64]; char *space_p = 0; char linked_remote_system[CALL_SIZE + 1]; char unlink_request[CALL_SIZE + 3]; @@ -1208,15 +947,14 @@ static void runit() char tmp1[CALL_SIZE + 1]; char tmp2[36]; // 8 for rpt1 + 24 for time_t in std::string format - unsigned char readBuffer[100]; // 58 or 29 or 32, max is 58 - unsigned char readBuffer2[1024]; unsigned char dcs_buf[1000];; char call[CALL_SIZE + 1]; - char ip[IP_SIZE + 1]; - inbound *inbound_ptr; + char ip[INET6_ADDRSTRLEN + 1]; bool found = false; + unsigned char your[3] = { 'C', 'C', 'C' }; + char cmd_2_dcs[23]; unsigned char dcs_seq[3] = { 0x00, 0x00, 0x00 }; struct { @@ -1251,111 +989,108 @@ static void runit() char source_stn[9]; - memset(notify_msg, '\0', sizeof(notify_msg)); time(&hb); if (xrf_g2_sock > max_nfds) max_nfds = xrf_g2_sock; if (ref_g2_sock > max_nfds) max_nfds = ref_g2_sock; - if (rptr_sock > max_nfds) - max_nfds = rptr_sock; if (dcs_g2_sock > max_nfds) max_nfds = dcs_g2_sock; - - printf("xrf=%d, dcs=%d, ref=%d, rptr=%d, MAX+1=%d\n", - xrf_g2_sock, dcs_g2_sock, ref_g2_sock, rptr_sock, max_nfds + 1); - - if (strlen(link_at_startup) >= 8) { - if ((link_at_startup[0] == 'A') || (link_at_startup[0] == 'B') || (link_at_startup[0] == 'C')) { - memset(temp_repeater, ' ', CALL_SIZE); - memcpy(temp_repeater, link_at_startup + 1, 6); - temp_repeater[CALL_SIZE] = '\0'; - printf("sleep for 15 before link at startup\n"); - sleep(15); - g2link(link_at_startup[0], temp_repeater, link_at_startup[7]); + if (Gate2Link.GetFD() > max_nfds) + max_nfds = Gate2Link.GetFD(); + + printf("xrf=%d, dcs=%d, ref=%d, gateway=%d, MAX+1=%d\n", xrf_g2_sock, dcs_g2_sock, ref_g2_sock, Gate2Link.GetFD(), max_nfds + 1); + + // initialize all request links + bool first = true; + for (int i=0; i<3; i++) { + if (8 == link_at_startup[i].length()) { + if (first) { + printf("sleep for 15 sec before link at startup\n"); + sleep(15); + first = false; + } + std::string node(link_at_startup[i].substr(0, 6)); + node.resize(CALL_SIZE, ' '); + g2link('A'+i, node.c_str(), link_at_startup[i].at(7)); } - memset(link_at_startup, '\0', sizeof(link_at_startup)); } while (keep_running) { time(&tnow); - if ((tnow - hb) > 0) { + if (keep_running && (tnow - hb) > 0) { /* send heartbeat to connected donglers */ send_heartbeat(); /* send heartbeat to linked XRF repeaters/reflectors */ - if (to_remote_g2[0].toDst4.sin_port == htons(rmt_xrf_port)) - sendto(xrf_g2_sock, owner.c_str(), CALL_SIZE+1, 0, (struct sockaddr *)&(to_remote_g2[0].toDst4), sizeof(to_remote_g2[0].toDst4)); + if (to_remote_g2[0].addr.GetPort() == rmt_xrf_port) + sendto(xrf_g2_sock, owner.c_str(), CALL_SIZE+1, 0, to_remote_g2[0].addr.GetPointer(), to_remote_g2[0].addr.GetSize()); - if ((to_remote_g2[1].toDst4.sin_port == htons(rmt_xrf_port)) && (strcmp(to_remote_g2[1].to_call, to_remote_g2[0].to_call) != 0)) - sendto(xrf_g2_sock, owner.c_str(), CALL_SIZE+1, 0, (struct sockaddr *)&(to_remote_g2[1].toDst4), sizeof(to_remote_g2[1].toDst4)); + if ((to_remote_g2[1].addr.GetPort() == rmt_xrf_port) && (strcmp(to_remote_g2[1].cs, to_remote_g2[0].cs) != 0)) + sendto(xrf_g2_sock, owner.c_str(), CALL_SIZE+1, 0, to_remote_g2[1].addr.GetPointer(), to_remote_g2[1].addr.GetSize()); - if ((to_remote_g2[2].toDst4.sin_port == htons(rmt_xrf_port)) && - (strcmp(to_remote_g2[2].to_call, to_remote_g2[0].to_call) != 0) && - (strcmp(to_remote_g2[2].to_call, to_remote_g2[1].to_call) != 0)) - sendto(xrf_g2_sock, owner.c_str(), CALL_SIZE+1, 0, (struct sockaddr *)&(to_remote_g2[2].toDst4), sizeof(to_remote_g2[2].toDst4)); + if ((to_remote_g2[2].addr.GetPort() == rmt_xrf_port) && (strcmp(to_remote_g2[2].cs, to_remote_g2[0].cs) != 0) && (strcmp(to_remote_g2[2].cs, to_remote_g2[1].cs) != 0)) + sendto(xrf_g2_sock, owner.c_str(), CALL_SIZE+1, 0, to_remote_g2[2].addr.GetPointer(), to_remote_g2[2].addr.GetSize()); /* send heartbeat to linked DCS reflectors */ - if (to_remote_g2[0].toDst4.sin_port == htons(rmt_dcs_port)) { + if (to_remote_g2[0].addr.GetPort() == rmt_dcs_port) { strcpy(cmd_2_dcs, owner.c_str()); cmd_2_dcs[7] = to_remote_g2[0].from_mod; - memcpy(cmd_2_dcs + 9, to_remote_g2[0].to_call, 8); + memcpy(cmd_2_dcs + 9, to_remote_g2[0].cs, 8); cmd_2_dcs[16] = to_remote_g2[0].to_mod; - sendto(dcs_g2_sock, cmd_2_dcs, 17, 0, (struct sockaddr *)&(to_remote_g2[0].toDst4), sizeof(to_remote_g2[0].toDst4)); + sendto(dcs_g2_sock, cmd_2_dcs, 17, 0, to_remote_g2[0].addr.GetPointer(), to_remote_g2[0].addr.GetSize()); } - if (to_remote_g2[1].toDst4.sin_port == htons(rmt_dcs_port)) { + if (to_remote_g2[1].addr.GetPort() == rmt_dcs_port) { strcpy(cmd_2_dcs, owner.c_str()); cmd_2_dcs[7] = to_remote_g2[1].from_mod; - memcpy(cmd_2_dcs + 9, to_remote_g2[1].to_call, 8); + memcpy(cmd_2_dcs + 9, to_remote_g2[1].cs, 8); cmd_2_dcs[16] = to_remote_g2[1].to_mod; - sendto(dcs_g2_sock, cmd_2_dcs, 17, 0, (struct sockaddr *)&(to_remote_g2[1].toDst4), sizeof(to_remote_g2[1].toDst4)); + sendto(dcs_g2_sock, cmd_2_dcs, 17, 0, to_remote_g2[1].addr.GetPointer(), to_remote_g2[1].addr.GetSize()); } - if (to_remote_g2[2].toDst4.sin_port == htons(rmt_dcs_port)) { + if (to_remote_g2[2].addr.GetPort() == rmt_dcs_port) { strcpy(cmd_2_dcs, owner.c_str()); cmd_2_dcs[7] = to_remote_g2[2].from_mod; - memcpy(cmd_2_dcs + 9, to_remote_g2[2].to_call, 8); + memcpy(cmd_2_dcs + 9, to_remote_g2[2].cs, 8); cmd_2_dcs[16] = to_remote_g2[2].to_mod; - sendto(dcs_g2_sock, cmd_2_dcs, 17, 0, (struct sockaddr *)&(to_remote_g2[2].toDst4), sizeof(to_remote_g2[2].toDst4)); + sendto(dcs_g2_sock, cmd_2_dcs, 17, 0, to_remote_g2[2].addr.GetPointer(), to_remote_g2[2].addr.GetSize()); } /* send heartbeat to linked REF reflectors */ - if (to_remote_g2[0].is_connected && (to_remote_g2[0].toDst4.sin_port == htons(rmt_ref_port))) - sendto(ref_g2_sock, REF_ACK, 3, 0, (struct sockaddr *)&(to_remote_g2[0].toDst4), sizeof(to_remote_g2[0].toDst4)); + if (to_remote_g2[0].is_connected && to_remote_g2[0].addr.GetPort()==rmt_ref_port) + sendto(ref_g2_sock, REF_ACK, 3, 0, to_remote_g2[0].addr.GetPointer(), to_remote_g2[0].addr.GetSize()); - if (to_remote_g2[1].is_connected && - (to_remote_g2[1].toDst4.sin_port == htons(rmt_ref_port)) && - (strcmp(to_remote_g2[1].to_call, to_remote_g2[0].to_call) != 0)) - sendto(ref_g2_sock, REF_ACK, 3, 0, (struct sockaddr *)&(to_remote_g2[1].toDst4), sizeof(to_remote_g2[1].toDst4)); + if (to_remote_g2[1].is_connected && to_remote_g2[1].addr.GetPort()==rmt_ref_port && strcmp(to_remote_g2[1].cs, to_remote_g2[0].cs)) + sendto(ref_g2_sock, REF_ACK, 3, 0, to_remote_g2[1].addr.GetPointer(), to_remote_g2[1].addr.GetSize()); - if (to_remote_g2[2].is_connected && - (to_remote_g2[2].toDst4.sin_port == htons(rmt_ref_port)) && - (strcmp(to_remote_g2[2].to_call, to_remote_g2[0].to_call) != 0) && - (strcmp(to_remote_g2[2].to_call, to_remote_g2[1].to_call) != 0)) - sendto(ref_g2_sock, REF_ACK, 3, 0, (struct sockaddr *)&(to_remote_g2[2].toDst4), sizeof(to_remote_g2[2].toDst4)); + if (to_remote_g2[2].is_connected && to_remote_g2[2].addr.GetPort()==rmt_ref_port && strcmp(to_remote_g2[2].cs, to_remote_g2[0].cs) && strcmp(to_remote_g2[2].cs, to_remote_g2[1].cs)) + sendto(ref_g2_sock, REF_ACK, 3, 0, to_remote_g2[2].addr.GetPointer(), to_remote_g2[2].addr.GetSize()); - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { /* check for timeouts from remote */ - if (to_remote_g2[i].to_call[0] != '\0') { + if (to_remote_g2[i].cs[0] != '\0') { if (to_remote_g2[i].countdown >= 0) to_remote_g2[i].countdown--; if (to_remote_g2[i].countdown < 0) { /* maybe remote system has changed IP */ - printf("Unlinked from [%s] mod %c, TIMEOUT...\n", - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); + printf("Unlinked from [%s] mod %c, TIMEOUT...\n", to_remote_g2[i].cs, to_remote_g2[i].to_mod); - sprintf(notify_msg, "%c_unlinked.dat_UNLINKED_TIMEOUT", to_remote_g2[i].from_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_unlinked.dat_LINK_TIMEOUT", to_remote_g2[i].from_mod); + + if (to_remote_g2[i].auto_link) { + char cs[CALL_SIZE+1]; + memcpy(cs, to_remote_g2[i].cs, CALL_SIZE+1); // call is passed by pointer so we have to copy it + g2link(to_remote_g2[i].from_mod, cs, to_remote_g2[i].to_mod); + } else { + to_remote_g2[i].cs[0] = '\0'; + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; + to_remote_g2[i].addr.Clear(); + to_remote_g2[i].countdown = 0; + to_remote_g2[i].is_connected = false; + to_remote_g2[i].in_streamid = 0x0; + } - to_remote_g2[i].to_call[0] = '\0'; - memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; - to_remote_g2[i].countdown = 0; - to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; print_status_file(); @@ -1367,62 +1102,57 @@ static void runit() if (((tnow - tracing[i].last_time) > rf_inactivity_timer[i]) && (rf_inactivity_timer[i] > 0)) { tracing[i].last_time = 0; - printf("Unlinked from [%s] mod %c, local RF inactivity...\n", - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); + printf("Unlinked from [%s] mod %c, local RF inactivity...\n", to_remote_g2[i].cs, to_remote_g2[i].to_mod); - if (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port)) { + if (to_remote_g2[i].addr.GetPort() == rmt_ref_port) { queryCommand[0] = 5; queryCommand[1] = 0; queryCommand[2] = 24; queryCommand[3] = 0; queryCommand[4] = 0; - sendto(ref_g2_sock, queryCommand, 5, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); + sendto(ref_g2_sock, queryCommand, 5, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); /* zero out any other entries here that match that system */ - for (j = 0; j < 3; j++) { + for (int j=0; j<3; j++) { if (j != i) { - if ((to_remote_g2[j].toDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[j].toDst4.sin_port == htons(rmt_ref_port))) { - to_remote_g2[j].to_call[0] = '\0'; - memset(&(to_remote_g2[j].toDst4),0,sizeof(struct sockaddr_in)); + if (to_remote_g2[j].addr == to_remote_g2[i].addr) { + to_remote_g2[j].cs[0] = '\0'; + to_remote_g2[j].addr.Clear(); to_remote_g2[j].from_mod = ' '; to_remote_g2[j].to_mod = ' '; to_remote_g2[j].countdown = 0; to_remote_g2[j].is_connected = false; - to_remote_g2[j].in_streamid[0] = to_remote_g2[j].in_streamid[1] = 0x00; + to_remote_g2[j].in_streamid = 0x0; } } } - } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) { + } else if (to_remote_g2[i].addr.GetPort() == rmt_xrf_port) { strcpy(unlink_request, owner.c_str()); unlink_request[8] = to_remote_g2[i].from_mod; unlink_request[9] = ' '; unlink_request[10] = '\0'; - for (j = 0; j < 5; j++) - sendto(xrf_g2_sock, unlink_request, CALL_SIZE+3, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); - } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) { + for (int j=0; j<5; j++) + sendto(xrf_g2_sock, unlink_request, CALL_SIZE+3, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); + } else if (to_remote_g2[i].addr.GetPort() == rmt_dcs_port) { strcpy(cmd_2_dcs, owner.c_str()); cmd_2_dcs[8] = to_remote_g2[i].from_mod; cmd_2_dcs[9] = ' '; cmd_2_dcs[10] = '\0'; - memcpy(cmd_2_dcs + 11, to_remote_g2[i].to_call, 8); + memcpy(cmd_2_dcs + 11, to_remote_g2[i].cs, 8); - for (j=0; j<2; j++) - sendto(dcs_g2_sock, cmd_2_dcs, 19 ,0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); + for (int j=0; j<2; j++) + sendto(dcs_g2_sock, cmd_2_dcs, 19 ,0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); } - sprintf(notify_msg, "%c_unlinked.dat_UNLINKED_TIMEOUT", to_remote_g2[i].from_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_unlinked.dat_UNLINKED_TIMEOUT", to_remote_g2[i].from_mod); - to_remote_g2[i].to_call[0] = '\0'; - memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; + to_remote_g2[i].cs[0] = '\0'; + to_remote_g2[i].addr.Clear(); + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; print_status_file(); } @@ -1431,143 +1161,136 @@ static void runit() time(&hb); } + // play a qnvoice file if it is specified + // this could be coming from qnvoice or qngateway (connected2network or notincache) + std::ifstream voicefile(qnvoice_file.c_str(), std::ifstream::in); + if (voicefile) { + if (keep_running) { + char line[FILENAME_MAX]; + voicefile.getline(line, FILENAME_MAX); + // trim whitespace + char *start = line; + while (isspace(*start)) + start++; + char *end = start + strlen(start) - 1; + while (isspace(*end)) + *end-- = (char)0; + // anthing reasonable left? + if (strlen(start) > 2) + PlayAudioNotifyThread(start); + } + //clean-up + voicefile.close(); + remove(qnvoice_file.c_str()); + } + FD_ZERO(&fdset); - FD_SET(xrf_g2_sock,&fdset); - FD_SET(dcs_g2_sock,&fdset); - FD_SET(ref_g2_sock,&fdset); - FD_SET(rptr_sock,&fdset); + FD_SET(xrf_g2_sock, &fdset); + FD_SET(dcs_g2_sock, &fdset); + FD_SET(ref_g2_sock, &fdset); + FD_SET(Gate2Link.GetFD(), &fdset); tv.tv_sec = 0; tv.tv_usec = 20000; - (void)select(max_nfds + 1,&fdset,0,0,&tv); + (void)select(max_nfds + 1, &fdset, 0, 0, &tv); - if (FD_ISSET(xrf_g2_sock, &fdset)) { - fromlen = sizeof(struct sockaddr_in); - recvlen2 = recvfrom(xrf_g2_sock, (char *)readBuffer2, 100, 0, (struct sockaddr *)&fromDst4, &fromlen); + if (keep_running && FD_ISSET(xrf_g2_sock, &fdset)) { + socklen_t fromlen = sizeof(struct sockaddr_in); + unsigned char buf[100]; + int length = recvfrom(xrf_g2_sock, buf, 100, 0, fromDst4.GetPointer(), &fromlen); - strncpy(ip, inet_ntoa(fromDst4.sin_addr),IP_SIZE); - ip[IP_SIZE] = '\0'; - strncpy(call, (char *)readBuffer2,CALL_SIZE); + strncpy(ip, fromDst4.GetAddress(), INET6_ADDRSTRLEN); + ip[INET6_ADDRSTRLEN] = '\0'; + memcpy(call, buf, CALL_SIZE); call[CALL_SIZE] = '\0'; - /* a packet of length (CALL_SIZE + 1) is a keepalive from a repeater/reflector */ + /* A packet of length (CALL_SIZE + 1) is a keepalive from a repeater/reflector */ /* If it is from a dongle, it is either a keepalive or a request to connect */ - if (recvlen2 == (CALL_SIZE + 1)) { + if (length == (CALL_SIZE + 1)) { found = false; /* Find out if it is a keepalive from a repeater */ - for (i = 0; i < 3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port))) { + for (int i=0; i<3; i++) { + if (fromDst4==to_remote_g2[i].addr && to_remote_g2[i].addr.GetPort()==rmt_xrf_port) { found = true; if (!to_remote_g2[i].is_connected) { tracing[i].last_time = time(NULL); to_remote_g2[i].is_connected = true; - printf("Connected from: %.*s\n", recvlen2 - 1, readBuffer2); + printf("Connected from: %.*s\n", length - 1, buf); print_status_file(); - strcpy(linked_remote_system, to_remote_g2[i].to_call); + strcpy(linked_remote_system, to_remote_g2[i].cs); space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", - to_remote_g2[i].from_mod, - linked_remote_system, - to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); } to_remote_g2[i].countdown = TIMEOUT; } } - } else if (recvlen2 == (CALL_SIZE + 6)) { + } else if (length == (CALL_SIZE + 6)) { /* A packet of length (CALL_SIZE + 6) is either an ACK or a NAK from repeater-reflector */ /* Because we sent a request before asking to link */ - for (i = 0; i < 3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port))) { - if ((memcmp((char *)readBuffer2 + 10, "ACK", 3) == 0) && - (to_remote_g2[i].from_mod == readBuffer2[8])) { - if (!to_remote_g2[i].is_connected) { - tracing[i].last_time = time(NULL); - - to_remote_g2[i].is_connected = true; - printf("Connected from: [%s] %c\n", - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - print_status_file(); - - strcpy(linked_remote_system, to_remote_g2[i].to_call); - space_p = strchr(linked_remote_system, ' '); - if (space_p) - *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", - to_remote_g2[i].from_mod, - linked_remote_system, - to_remote_g2[i].to_mod); - audio_notify(notify_msg); - } - } else if ((memcmp((char *)readBuffer2 + 10, "NAK", 3) == 0) && - (to_remote_g2[i].from_mod == readBuffer2[8])) { - printf("Link module %c to [%s] %c is rejected\n", - to_remote_g2[i].from_mod, to_remote_g2[i].to_call, - to_remote_g2[i].to_mod); - - sprintf(notify_msg, "%c_failed_linked.dat_FAILED_TO_LINK", - to_remote_g2[i].from_mod); - audio_notify(notify_msg); - - to_remote_g2[i].to_call[0] = '\0'; - memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; - to_remote_g2[i].countdown = 0; - to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + for (int i=0; i<3; i++) { + if ((fromDst4==to_remote_g2[i].addr && to_remote_g2[i].addr.GetPort()==rmt_xrf_port)) { + if (0==memcmp(buf + 10, "ACK", 3) && to_remote_g2[i].from_mod==buf[8]) { + if (!to_remote_g2[i].is_connected) { + tracing[i].last_time = time(NULL); + to_remote_g2[i].is_connected = true; + printf("Connected from: [%s] %c\n", to_remote_g2[i].cs, to_remote_g2[i].to_mod); print_status_file(); + + strcpy(linked_remote_system, to_remote_g2[i].cs); + space_p = strchr(linked_remote_system, ' '); + if (space_p) + *space_p = '\0'; + sprintf(notify_msg[i], "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); } + } else if (0==memcmp(buf + 10, "NAK", 3) && to_remote_g2[i].from_mod==buf[8]) { + printf("Link module %c to [%s] %c is rejected\n", to_remote_g2[i].from_mod, to_remote_g2[i].cs, to_remote_g2[i].to_mod); + + sprintf(notify_msg[i], "%c_failed_link.dat_FAILED_TO_LINK", to_remote_g2[i].from_mod); + + to_remote_g2[i].cs[0] = '\0'; + to_remote_g2[i].addr.Clear(); + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; + to_remote_g2[i].countdown = 0; + to_remote_g2[i].is_connected = false; + to_remote_g2[i].in_streamid = 0x0; + + print_status_file(); } } - } else if (recvlen2 == CALL_SIZE + 3) { - /* - A packet of length (CALL_SIZE + 3) is a request - from a remote repeater to link-unlink with our repeater - */ + } + } else if (length == CALL_SIZE + 3) { + // A packet of length (CALL_SIZE + 3) is a request + // from a remote repeater to link-unlink with our repeater /* Check our linked repeaters/reflectors */ - for (i = 0; i < 3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port))) { - if (to_remote_g2[i].to_mod == readBuffer2[8]) { + for (int i=0; i<3; i++) { + if (fromDst4==to_remote_g2[i].addr && to_remote_g2[i].addr.GetPort()==rmt_xrf_port) { + if (to_remote_g2[i].to_mod == buf[8]) { /* unlink request from remote repeater that we know */ - if (readBuffer2[9] == ' ') { - printf("Received: %.*s\n", recvlen2 - 1, readBuffer2); - printf("Module %c to [%s] %c is unlinked\n", - to_remote_g2[i].from_mod, - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - - sprintf(notify_msg, "%c_unlinked.dat_UNLINKED", to_remote_g2[i].from_mod); - audio_notify(notify_msg); - - to_remote_g2[i].to_call[0] = '\0'; - memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; + if (buf[9] == ' ') { + printf("Received: %.*s\n", length - 1, buf); + printf("Module %c to [%s] %c is unlinked\n", to_remote_g2[i].from_mod, to_remote_g2[i].cs, to_remote_g2[i].to_mod); + + sprintf(notify_msg[i], "%c_unlinked.dat_UNLINKED", to_remote_g2[i].from_mod); + + to_remote_g2[i].cs[0] = '\0'; + to_remote_g2[i].addr.Clear(); + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; print_status_file(); } else /* link request from a remote repeater that we know */ - if ( - ((i == 0) && (readBuffer2[9] == 'A')) || - ((i == 1) && (readBuffer2[9] == 'B')) || - ((i == 2) && (readBuffer2[9] == 'C')) - ) { + if ((i==0 && buf[9]=='A') || (i==1 && buf[9]=='B') || (i==2 && buf[9]=='C')) { /* I HAVE TO ADD CODE here to PREVENT the REMOTE NODE @@ -1575,42 +1298,34 @@ static void runit() more than one of our local modules */ - printf("Received: %.*s\n", recvlen2 - 1, readBuffer2); + printf("Received: %.*s\n", length - 1, buf); - strncpy(to_remote_g2[i].to_call, (char *)readBuffer2,CALL_SIZE); - to_remote_g2[i].to_call[CALL_SIZE] = '\0'; - memcpy(&(to_remote_g2[i].toDst4), &fromDst4, sizeof(struct sockaddr_in)); - to_remote_g2[i].toDst4.sin_port = htons(rmt_xrf_port); - to_remote_g2[i].to_mod = readBuffer2[8]; + memcpy(to_remote_g2[i].cs, buf, CALL_SIZE); + to_remote_g2[i].cs[CALL_SIZE] = '\0'; + to_remote_g2[i].addr = fromDst4; + to_remote_g2[i].to_mod = buf[8]; to_remote_g2[i].countdown = TIMEOUT; to_remote_g2[i].is_connected = true; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; - printf("Module %c to [%s] %c linked\n", - readBuffer2[9], - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); + printf("Module %c to [%s] %c linked\n", buf[9], to_remote_g2[i].cs, to_remote_g2[i].to_mod); tracing[i].last_time = time(NULL); print_status_file(); /* send back an ACK */ - memcpy(readBuffer2 + 10, "ACK", 4); - sendto(xrf_g2_sock, readBuffer2, CALL_SIZE+6, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + memcpy(buf + 10, "ACK", 4); + sendto(xrf_g2_sock, buf, CALL_SIZE+6, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); - if (to_remote_g2[i].from_mod != readBuffer2[9]) { - to_remote_g2[i].from_mod = readBuffer2[9]; + if (to_remote_g2[i].from_mod != buf[9]) { + to_remote_g2[i].from_mod = buf[9]; - strcpy(linked_remote_system, to_remote_g2[i].to_call); + strcpy(linked_remote_system, to_remote_g2[i].cs); space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", - to_remote_g2[i].from_mod, - linked_remote_system, - to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); } } } @@ -1619,22 +1334,22 @@ static void runit() /* link request from remote repeater that is not yet linked to our system */ /* find out which of our local modules the remote repeater is interested in */ - i = -1; - if (readBuffer2[9] == 'A') + int i = -1; + if (buf[9] == 'A') i = 0; - else if (readBuffer2[9] == 'B') + else if (buf[9] == 'B') i = 1; - else if (readBuffer2[9] == 'C') + else if (buf[9] == 'C') i = 2; /* Is this repeater listed in gwys.txt? */ auto gwy_pos = gwy_list.find(call); if (gwy_pos == gwy_list.end()) { /* We did NOT find this repeater in gwys.txt, reject the incoming link request */ - printf("Incoming link from %s,%s but not found in gwys.txt\n",call,ip); + printf("Incoming link from %s,%s but not found in gwys.txt\n", call, ip); i = -1; } else { - rc = regexec(&preg, call, 0, NULL, 0); + int rc = regexec(&preg, call, 0, NULL, 0); if (rc != 0) { printf("Invalid repeater %s,%s requesting to link\n", call, ip); i = -1; @@ -1644,8 +1359,7 @@ static void runit() if (i >= 0) { /* Is the local repeater module linked to anything ? */ if (to_remote_g2[i].to_mod == ' ') { - if ((readBuffer2[8] == 'A') || (readBuffer2[8] == 'B') || (readBuffer2[8] == 'C') || - (readBuffer2[8] == 'D') || (readBuffer2[8] == 'E')) { + if (buf[8]>='A' && buf[8]<='E') { /* I HAVE TO ADD CODE here to PREVENT the REMOTE NODE from LINKING one of their remote modules to @@ -1653,101 +1367,82 @@ static void runit() */ /* now it can be added as a repeater */ - strcpy(to_remote_g2[i].to_call, call); - to_remote_g2[i].to_call[CALL_SIZE] = '\0'; - memcpy(&(to_remote_g2[i].toDst4), &fromDst4, sizeof(struct sockaddr_in)); - to_remote_g2[i].toDst4.sin_port = htons(rmt_xrf_port); - to_remote_g2[i].from_mod = readBuffer2[9]; - to_remote_g2[i].to_mod = readBuffer2[8]; + strcpy(to_remote_g2[i].cs, call); + to_remote_g2[i].cs[CALL_SIZE] = '\0'; + to_remote_g2[i].addr = fromDst4; + to_remote_g2[i].from_mod = buf[9]; + to_remote_g2[i].to_mod = buf[8]; to_remote_g2[i].countdown = TIMEOUT; to_remote_g2[i].is_connected = true; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; print_status_file(); tracing[i].last_time = time(NULL); - printf("Received: %.*s\n", recvlen2 - 1, readBuffer2); - printf("Module %c to [%s] %c linked\n", - to_remote_g2[i].from_mod, - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); + printf("Received: %.*s\n", length - 1, buf); + printf("Module %c to [%s] %c linked\n", to_remote_g2[i].from_mod, to_remote_g2[i].cs, to_remote_g2[i].to_mod); - strcpy(linked_remote_system, to_remote_g2[i].to_call); + strcpy(linked_remote_system, to_remote_g2[i].cs); space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", - to_remote_g2[i].from_mod, - linked_remote_system, - to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); /* send back an ACK */ - memcpy(readBuffer2 + 10, "ACK", 4); - sendto(xrf_g2_sock, readBuffer2, CALL_SIZE+6, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + memcpy(buf + 10, "ACK", 4); + sendto(xrf_g2_sock, buf, CALL_SIZE+6, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); } } else { - if (fromDst4.sin_addr.s_addr != to_remote_g2[i].toDst4.sin_addr.s_addr) { + if (! (fromDst4 == to_remote_g2[i].addr)) { /* Our repeater module is linked to another repeater-reflector */ - memcpy(readBuffer2 + 10, "NAK", 4); - fromDst4.sin_port = htons(rmt_xrf_port); - sendto(xrf_g2_sock, readBuffer2, CALL_SIZE+6, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + memcpy(buf + 10, "NAK", 4); + if (fromDst4.GetPort() != rmt_xrf_port) { + fromDst4.Initialize(fromDst4.GetFamily(), rmt_xrf_port, fromDst4.GetAddress()); + } + sendto(xrf_g2_sock, buf, CALL_SIZE+6, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); } } } - } else if ( ((recvlen2 == 56) || (recvlen2 == 27)) && - (0 == memcmp(readBuffer2, "DSVT", 4)) && - ((readBuffer2[4] == 0x10) || (readBuffer2[4] == 0x20)) && - (readBuffer2[8] == 0x20)) { + } else if ((length==56 || length==27) && 0==memcmp(buf, "DSVT", 4) && (buf[4]==0x10 || buf[4]==0x20) && buf[8]==0x20) { /* reset countdown and protect against hackers */ found = false; - for (i = 0; i < 3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port))) { + for (int i=0; i<3; i++) { + if ((fromDst4 == to_remote_g2[i].addr) && (to_remote_g2[i].addr.GetPort() == rmt_xrf_port)) { to_remote_g2[i].countdown = TIMEOUT; found = true; } } - /* process header */ + SDSVT dsvt; memcpy(dsvt.title, buf, length); // copy to struct - if ((recvlen2 == 56) && found) { + /* process header */ + if ((length == 56) && found) { memset(source_stn, ' ', 9); source_stn[8] = '\0'; /* some bad hotspot programs out there using INCORRECT flag */ - if (readBuffer2[15] == 0x40) - readBuffer2[15] = 0x00; - else if (readBuffer2[15] == 0x48) - readBuffer2[15] = 0x08; - else if (readBuffer2[15] == 0x60) - readBuffer2[15] = 0x20; - else if (readBuffer2[15] == 0x68) - readBuffer2[15] = 0x28; + if (dsvt.hdr.flag[0]==0x40U || dsvt.hdr.flag[0]==0x48U || dsvt.hdr.flag[0]==0x60U || dsvt.hdr.flag[0]==0x68U) dsvt.hdr.flag[0] -= 0x40; /* A reflector will send to us its own RPT1 */ /* A repeater will send to us our RPT1 */ - for (i = 0; i < 3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port))) { + for (int i=0; i<3; i++) { + if (fromDst4==to_remote_g2[i].addr && to_remote_g2[i].addr.GetPort()==rmt_xrf_port) { /* it is a reflector, reflector's rpt1 */ - if ((memcmp(readBuffer2 + 18, to_remote_g2[i].to_call, 7) == 0) && - (readBuffer2[25] == to_remote_g2[i].to_mod)) { - memcpy(&readBuffer2[18], owner.c_str(), CALL_SIZE); - readBuffer2[25] = to_remote_g2[i].from_mod; - memcpy(&readBuffer2[34], "CQCQCQ ", 8); + if (0==memcmp(dsvt.hdr.rpt1, to_remote_g2[i].cs, 7) && dsvt.hdr.rpt1[7]==to_remote_g2[i].to_mod) { + memcpy(dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE); + dsvt.hdr.rpt1[7] = to_remote_g2[i].from_mod; + memcpy(dsvt.hdr.urcall, "CQCQCQ ", 8); - memcpy(source_stn, to_remote_g2[i].to_call, 8); + memcpy(source_stn, to_remote_g2[i].cs, 8); source_stn[7] = to_remote_g2[i].to_mod; break; } else /* it is a repeater, our rpt1 */ - if ((memcmp(readBuffer2 + 18, owner.c_str(), CALL_SIZE-1)) && - (readBuffer2[25] == to_remote_g2[i].from_mod)) { - memcpy(source_stn, to_remote_g2[i].to_call, 8); + if (memcmp(dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE-1) && dsvt.hdr.rpt1[7]==to_remote_g2[i].from_mod) { + memcpy(source_stn, to_remote_g2[i].cs, 8); source_stn[7] = to_remote_g2[i].to_mod; break; } @@ -1756,41 +1451,35 @@ static void runit() /* somebody's crazy idea of having a personal callsign in RPT2 */ /* we must set it to our gateway callsign */ - memcpy(&readBuffer2[26], owner.c_str(), CALL_SIZE); - readBuffer2[33] = 'G'; - calcPFCS(readBuffer2,56); + memcpy(dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE); + dsvt.hdr.rpt2[7] = 'G'; + calcPFCS(dsvt.title, 56); /* At this point, all data have our RPT1 and RPT2 */ /* send the data to the repeater/reflector that is linked to our RPT1 */ - i = -1; - if (readBuffer2[25] == 'A') + int i = -1; + if (dsvt.hdr.rpt1[7] == 'A') i = 0; - else if (readBuffer2[25] == 'B') + else if (dsvt.hdr.rpt1[7] == 'B') i = 1; - else if (readBuffer2[25] == 'C') + else if (dsvt.hdr.rpt1[7] == 'C') i = 2; /* are we sure that RPT1 is our system? */ - if ((memcmp(readBuffer2 + 18, owner.c_str(), CALL_SIZE-1) == 0) && (i >= 0)) { + if (0==memcmp(dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE-1) && i>=0) { /* Last Heard */ - if (memcmp(old_sid[i].sid, readBuffer2 + 12, 2) != 0) { + if (old_sid[i].sid != dsvt.streamid) { if (qso_details) - printf("START from remote g2: streamID=%d,%d, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s, source=%.8s\n", - readBuffer2[12],readBuffer2[13], - readBuffer2[15], readBuffer2[16], readBuffer2[17], - &readBuffer2[42], - &readBuffer2[50], &readBuffer2[34], - &readBuffer2[18], &readBuffer2[26], - recvlen2,inet_ntoa(fromDst4.sin_addr), source_stn); + printf("START from remote g2: streamID=%04x, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s, source=%.8s\n", ntohs(dsvt.streamid), dsvt.hdr.flag[0], dsvt.hdr.flag[1], dsvt.hdr.flag[2], dsvt.hdr.mycall, dsvt.hdr.sfx, dsvt.hdr.urcall, dsvt.hdr.rpt1, dsvt.hdr.rpt2, length, fromDst4.GetAddress(), source_stn); // put user into tmp1 - memcpy(tmp1, readBuffer2 + 42, 8); + memcpy(tmp1, dsvt.hdr.mycall, 8); tmp1[8] = '\0'; // delete the user if exists for (auto dt_lh_pos = dt_lh_list.begin(); dt_lh_pos != dt_lh_list.end(); dt_lh_pos++) { - if (strcmp((char *)dt_lh_pos->second.c_str(), tmp1) == 0) { + if (0 == strcmp((char *)dt_lh_pos->second.c_str(), tmp1)) { dt_lh_list.erase(dt_lh_pos); break; } @@ -1802,166 +1491,157 @@ static void runit() } // add user time(&tnow); - sprintf(tmp2, "%ld=r%.6s%c%c", tnow, source_stn, source_stn[7], readBuffer2[25]); + sprintf(tmp2, "%ld=r%.6s%c%c", tnow, source_stn, source_stn[7], dsvt.hdr.rpt1[7]); dt_lh_list[tmp2] = tmp1; - memcpy(old_sid[i].sid, readBuffer2 + 12, 2); + old_sid[i].sid = dsvt.streamid; } /* relay data to our local G2 */ - sendto(rptr_sock, readBuffer2,56,0,(struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); + Link2Gate.Write(dsvt.title, 56); /* send data to donglers */ /* no changes here */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; - if (fromDst4.sin_addr.s_addr != inbound_ptr->sin.sin_addr.s_addr) { - readBuffer[0] = (unsigned char)(58 & 0xFF); - readBuffer[1] = (unsigned char)(58 >> 8 & 0x1F); - readBuffer[1] = (unsigned char)(readBuffer[1] | 0xFFFFFF80); - memcpy(readBuffer + 2, readBuffer2, 56); - - sendto(ref_g2_sock, readBuffer, 58, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + SINBOUND *inbound = (SINBOUND *)pos->second; + if (! (fromDst4 == inbound->addr)) { + SREFDSVT rdsvt; + rdsvt.head[0] = (unsigned char)(58 & 0xFF); + rdsvt.head[1] = (unsigned char)(58 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); + memcpy(rdsvt.dsvt.title, dsvt.title, 56); + + sendto(ref_g2_sock, rdsvt.head, 58, 0, inbound->addr.GetPointer(), inbound->addr.GetSize()); } else - inbound_ptr->mod = readBuffer2[25]; + inbound->mod = dsvt.hdr.rpt1[7]; } /* send the data to the repeater/reflector that is linked to our RPT1 */ /* Is there another local module linked to the remote same xrf mod ? */ /* If Yes, then broadcast */ - k = i + 1; + int k = i + 1; if (k < 3) { brd_from_xrf_idx = 0; - streamid_raw = (readBuffer2[12] * 256U) + readBuffer2[13]; + streamid_raw = ntohs(dsvt.streamid); /* We can only enter this loop up to 2 times max */ - for (j = k; j < 3; j++) { + for (int j=k; j<3; j++) { /* it is a remote gateway, not a dongle user */ - if ((fromDst4.sin_addr.s_addr == to_remote_g2[j].toDst4.sin_addr.s_addr) && + if (fromDst4==to_remote_g2[j].addr && /* it is xrf */ - (to_remote_g2[j].toDst4.sin_port == htons(rmt_xrf_port)) && - (memcmp(to_remote_g2[j].to_call, "XRF", 3) == 0) && + to_remote_g2[j].addr.GetPort()==rmt_xrf_port && + 0==memcmp(to_remote_g2[j].cs, "XRF", 3) && /* it is the same xrf and xrf module */ - (memcmp(to_remote_g2[j].to_call, to_remote_g2[i].to_call, 8) == 0) && - (to_remote_g2[j].to_mod == to_remote_g2[i].to_mod)) { + 0==memcmp(to_remote_g2[j].cs, to_remote_g2[i].cs, 8) && + to_remote_g2[j].to_mod==to_remote_g2[i].to_mod) { /* send the packet to another module of our local repeater: this is multi-link */ /* generate new packet */ - memcpy(from_xrf_torptr_brd, readBuffer2, 56); + memcpy(from_xrf_torptr_brd.title, dsvt.title, 56); /* different repeater module */ - from_xrf_torptr_brd[25] = to_remote_g2[j].from_mod; + from_xrf_torptr_brd.hdr.rpt1[7] = to_remote_g2[j].from_mod; /* assign new streamid */ - streamid_raw ++; + streamid_raw++; if (streamid_raw == 0) - streamid_raw ++; - from_xrf_torptr_brd[12] = streamid_raw / 256U; - from_xrf_torptr_brd[13] = streamid_raw % 256U; + streamid_raw++; + from_xrf_torptr_brd.streamid = htons(streamid_raw); - calcPFCS(from_xrf_torptr_brd, 56); + calcPFCS(from_xrf_torptr_brd.title, 56); /* send the data to the local gateway/repeater */ - sendto(rptr_sock, from_xrf_torptr_brd, 56, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); + Link2Gate.Write(from_xrf_torptr_brd.title, 56); /* save streamid for use with the audio packets that will arrive after this header */ - brd_from_xrf.xrf_streamid[0] = readBuffer2[12]; - brd_from_xrf.xrf_streamid[1] = readBuffer2[13]; - brd_from_xrf.rptr_streamid[brd_from_xrf_idx][0] = from_xrf_torptr_brd[12]; - brd_from_xrf.rptr_streamid[brd_from_xrf_idx][1] = from_xrf_torptr_brd[13]; - brd_from_xrf_idx ++; + brd_from_xrf.xrf_streamid = dsvt.streamid; + brd_from_xrf.rptr_streamid[brd_from_xrf_idx] = from_xrf_torptr_brd.streamid; + brd_from_xrf_idx++; } } } - if ((to_remote_g2[i].toDst4.sin_addr.s_addr != fromDst4.sin_addr.s_addr) && - to_remote_g2[i].is_connected) { - if (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) { + if ((!(to_remote_g2[i].addr==fromDst4)) && to_remote_g2[i].is_connected) { + if (to_remote_g2[i].addr.GetPort() == rmt_xrf_port) { if ( /*** (memcmp(readBuffer2 + 42, owner, 8) != 0) && ***/ /* block repeater announcements */ - (memcmp(readBuffer2 + 34, "CQCQCQ", 6) == 0) && /* CQ calls only */ - ((readBuffer2[15] == 0x00) || /* normal */ - (readBuffer2[15] == 0x08) || /* EMR */ - (readBuffer2[15] == 0x20) || /* BK */ - (readBuffer2[15] == 0x28)) && /* EMR + BK */ - (memcmp(readBuffer2 + 26, owner.c_str(), CALL_SIZE-1) == 0) && /* rpt2 must be us */ - (readBuffer2[33] == 'G')) { - to_remote_g2[i].in_streamid[0] = readBuffer2[12]; - to_remote_g2[i].in_streamid[1] = readBuffer2[13]; + (memcmp(dsvt.hdr.urcall, "CQCQCQ", 6) == 0) && /* CQ calls only */ + (dsvt.hdr.flag[0] == 0x00 || /* normal */ + dsvt.hdr.flag[0] == 0x08 || /* EMR */ + dsvt.hdr.flag[0] == 0x20 || /* BK */ + dsvt.hdr.flag[0] == 0x28) && /* EMR + BK */ + 0==memcmp(dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE-1) && /* rpt2 must be us */ + dsvt.hdr.rpt2[7] == 'G') { + to_remote_g2[i].in_streamid = dsvt.streamid; /* inform XRF about the source */ - readBuffer2[11] = to_remote_g2[i].from_mod; + dsvt.flagb[2] = to_remote_g2[i].from_mod; - memcpy((char *)readBuffer2 + 18, to_remote_g2[i].to_call, CALL_SIZE); - readBuffer2[25] = to_remote_g2[i].to_mod; - memcpy((char *)readBuffer2 + 26, to_remote_g2[i].to_call, CALL_SIZE); - readBuffer2[33] = 'G'; - calcPFCS(readBuffer2, 56); + memcpy(dsvt.hdr.rpt1, to_remote_g2[i].cs, CALL_SIZE); + dsvt.hdr.rpt1[7] = to_remote_g2[i].to_mod; + memcpy(dsvt.hdr.rpt2, to_remote_g2[i].cs, CALL_SIZE); + dsvt.hdr.rpt2[7] = 'G'; + calcPFCS(dsvt.title, 56); - sendto(xrf_g2_sock, readBuffer2, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + sendto(xrf_g2_sock, dsvt.title, 56, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); } - } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port)) { + } else if (to_remote_g2[i].addr.GetPort() == rmt_ref_port) { if ( /*** (memcmp(readBuffer2 + 42, owner, 8) != 0) && ***/ /* block repeater announcements */ - (memcmp(readBuffer2 + 34, "CQCQCQ", 6) == 0) && /* CQ calls only */ - ((readBuffer2[15] == 0x00) || /* normal */ - (readBuffer2[15] == 0x08) || /* EMR */ - (readBuffer2[15] == 0x20) || /* BK */ - (readBuffer2[15] == 0x28)) && /* EMR + BK */ - (memcmp(readBuffer2 + 26, owner.c_str(), CALL_SIZE-1) == 0) && /* rpt2 must be us */ - (readBuffer2[33] == 'G')) { - to_remote_g2[i].in_streamid[0] = readBuffer2[12]; - to_remote_g2[i].in_streamid[1] = readBuffer2[13]; - - readBuffer[0] = (unsigned char)(58 & 0xFF); - readBuffer[1] = (unsigned char)(58 >> 8 & 0x1F); - readBuffer[1] = (unsigned char)(readBuffer[1] | 0xFFFFFF80); - - memcpy(readBuffer + 2, readBuffer2, 56); - - memset(readBuffer + 20, ' ', CALL_SIZE); - memcpy(readBuffer + 20, to_remote_g2[i].to_call, - strlen(to_remote_g2[i].to_call)); - readBuffer[27] = to_remote_g2[i].to_mod; - memset(readBuffer + 28, ' ', CALL_SIZE); - memcpy(readBuffer + 28, to_remote_g2[i].to_call, - strlen(to_remote_g2[i].to_call)); - readBuffer[35] = 'G'; - memcpy(&readBuffer[36], "CQCQCQ ", 8); - - calcPFCS(readBuffer + 2, 56); - - sendto(ref_g2_sock, readBuffer, 58, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + 0==memcmp(dsvt.hdr.urcall, "CQCQCQ", 6) && /* CQ calls only */ + (dsvt.hdr.flag[0] == 0x00 || /* normal */ + dsvt.hdr.flag[0] == 0x08 || /* EMR */ + dsvt.hdr.flag[0] == 0x20 || /* BK */ + dsvt.hdr.flag[0] == 0x28) && /* EMR + BK */ + 0==memcmp(dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE-1) && /* rpt2 must be us */ + dsvt.hdr.rpt2[7] == 'G') { + to_remote_g2[i].in_streamid = dsvt.streamid; + + SREFDSVT rdsvt; + rdsvt.head[0] = (unsigned char)(58 & 0xFF); + rdsvt.head[1] = (unsigned char)(58 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); + + memcpy(rdsvt.dsvt.title, dsvt.title, 56); + + memset(rdsvt.dsvt.hdr.rpt1, ' ', CALL_SIZE); + memcpy(rdsvt.dsvt.hdr.rpt1, to_remote_g2[i].cs, strlen(to_remote_g2[i].cs)); + rdsvt.dsvt.hdr.rpt1[7] = to_remote_g2[i].to_mod; + memset(rdsvt.dsvt.hdr.rpt2, ' ', CALL_SIZE); + memcpy(rdsvt.dsvt.hdr.rpt2, to_remote_g2[i].cs, strlen(to_remote_g2[i].cs)); + rdsvt.dsvt.hdr.rpt2[7] = 'G'; + memcpy(rdsvt.dsvt.hdr.urcall, "CQCQCQ ", CALL_SIZE); + + calcPFCS(rdsvt.dsvt.title, 56); + + sendto(ref_g2_sock, rdsvt.head, 58, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); } - } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) { + } else if (to_remote_g2[i].addr.GetPort() == rmt_dcs_port) { if ( /*** (memcmp(readBuffer2 + 42, owner, 8) != 0) && ***/ /* block repeater announcements */ - (memcmp(readBuffer2 + 34, "CQCQCQ", 6) == 0) && /* CQ calls only */ - ((readBuffer2[15] == 0x00) || /* normal */ - (readBuffer2[15] == 0x08) || /* EMR */ - (readBuffer2[15] == 0x20) || /* BK */ - (readBuffer2[15] == 0x28)) && /* EMR + BK */ - (memcmp(readBuffer2 + 26, owner.c_str(), CALL_SIZE-1) == 0) && /* rpt2 must be us */ - (readBuffer2[33] == 'G')) { - to_remote_g2[i].in_streamid[0] = readBuffer2[12]; - to_remote_g2[i].in_streamid[1] = readBuffer2[13]; - - memcpy(xrf_2_dcs[i].mycall, readBuffer2 + 42, 8); - memcpy(xrf_2_dcs[i].sfx, readBuffer2 + 50, 4); + 0==memcmp(dsvt.hdr.urcall, "CQCQCQ", 6) && /* CQ calls only */ + (dsvt.hdr.flag[0] == 0x00 || /* normal */ + dsvt.hdr.flag[0] == 0x08 || /* EMR */ + dsvt.hdr.flag[0] == 0x20 || /* BK */ + dsvt.hdr.flag[0] == 0x28) && /* EMR + BK */ + 0==memcmp(dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE-1) && /* rpt2 must be us */ + dsvt.hdr.rpt2[7] == 'G') { + to_remote_g2[i].in_streamid = dsvt.streamid; + + memcpy(xrf_2_dcs[i].mycall, dsvt.hdr.mycall, CALL_SIZE); + memcpy(xrf_2_dcs[i].sfx, dsvt.hdr.sfx, 4); xrf_2_dcs[i].dcs_rptr_seq = 0; } } } } - } else if (found) { - if ((readBuffer2[14] & 0x40) != 0) { - for (i = 0; i < 3; i++) { - if (memcmp(old_sid[i].sid, readBuffer2 + 12, 2) == 0) { + } else if (found) { // length is 27 + if ((dsvt.ctrl & 0x40) != 0) { + for (int i=0; i<3; i++) { + if (old_sid[i].sid == dsvt.streamid) { if (qso_details) - printf("END from remote g2: streamID=%d,%d, %d bytes from IP=%s\n", - readBuffer2[12],readBuffer2[13],recvlen2,inet_ntoa(fromDst4.sin_addr)); - - memset(old_sid[i].sid, 0x00, 2); + printf("END from remote g2: streamID=%04x, %d bytes from IP=%s\n", ntohs(dsvt.streamid), length, fromDst4.GetAddress()); + old_sid[i].sid = 0x0; break; } @@ -1969,98 +1649,90 @@ static void runit() } /* relay data to our local G2 */ - sendto(rptr_sock, readBuffer2, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + Link2Gate.Write(dsvt.title, 27); /* send data to donglers */ /* no changes here */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; - if (fromDst4.sin_addr.s_addr != inbound_ptr->sin.sin_addr.s_addr) { - readBuffer[0] = (unsigned char)(29 & 0xFF); - readBuffer[1] = (unsigned char)(29 >> 8 & 0x1F); - readBuffer[1] = (unsigned char)(readBuffer[1] | 0xFFFFFF80); + SINBOUND *inbound = (SINBOUND *)pos->second; + if (! (fromDst4 == inbound->addr)) { + SREFDSVT rdsvt; + rdsvt.head[0] = (unsigned char)(29 & 0xFF); + rdsvt.head[1] = (unsigned char)(29 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); - memcpy(readBuffer + 2, readBuffer2, 27); + memcpy(rdsvt.dsvt.title, dsvt.title, 27); - sendto(ref_g2_sock, readBuffer, 29, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, rdsvt.head, 29, 0, inbound->addr.GetPointer(), inbound->addr.GetSize()); } } /* do we have to broadcast ? */ - if (memcmp(brd_from_xrf.xrf_streamid, readBuffer2 + 12, 2) == 0) { - memcpy(from_xrf_torptr_brd, readBuffer2, 27); - - if ((brd_from_xrf.rptr_streamid[0][0] != 0x00) || - (brd_from_xrf.rptr_streamid[0][1] != 0x00)) { - from_xrf_torptr_brd[12] = brd_from_xrf.rptr_streamid[0][0]; - from_xrf_torptr_brd[13] = brd_from_xrf.rptr_streamid[0][1]; - sendto(rptr_sock, from_xrf_torptr_brd, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + if (brd_from_xrf.xrf_streamid == dsvt.streamid) { + memcpy(from_xrf_torptr_brd.title, dsvt.title, 27); + + if (brd_from_xrf.rptr_streamid[0] != 0x0) { + from_xrf_torptr_brd.streamid = brd_from_xrf.rptr_streamid[0]; + Link2Gate.Write(from_xrf_torptr_brd.title, 27); } - if ((brd_from_xrf.rptr_streamid[1][0] != 0x00) || - (brd_from_xrf.rptr_streamid[1][1] != 0x00)) { - from_xrf_torptr_brd[12] = brd_from_xrf.rptr_streamid[1][0]; - from_xrf_torptr_brd[13] = brd_from_xrf.rptr_streamid[1][1]; - sendto(rptr_sock, from_xrf_torptr_brd, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + if (brd_from_xrf.rptr_streamid[1] != 0x0) { + from_xrf_torptr_brd.streamid = brd_from_xrf.rptr_streamid[1]; + Link2Gate.Write(from_xrf_torptr_brd.title, 27); } - if ((readBuffer2[14] & 0x40) != 0) { - brd_from_xrf.xrf_streamid[0] = brd_from_xrf.xrf_streamid[1] = 0x00; - brd_from_xrf.rptr_streamid[0][0] = brd_from_xrf.rptr_streamid[0][1] = 0x00; - brd_from_xrf.rptr_streamid[1][0] = brd_from_xrf.rptr_streamid[1][1] = 0x00; + if (dsvt.ctrl & 0x40) { + brd_from_xrf.xrf_streamid = brd_from_xrf.rptr_streamid[0] = brd_from_xrf.rptr_streamid[1] = 0x0; brd_from_xrf_idx = 0; } } - for (i = 0; i < 3; i++) { - if ((to_remote_g2[i].is_connected) && - (to_remote_g2[i].toDst4.sin_addr.s_addr != fromDst4.sin_addr.s_addr) && - (memcmp(to_remote_g2[i].in_streamid, readBuffer2 + 12, 2) == 0)) { - if (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) { + for (int i=0; i<3; i++) { + if (to_remote_g2[i].is_connected && (! (to_remote_g2[i].addr==fromDst4)) && to_remote_g2[i].in_streamid==dsvt.streamid) { + if (to_remote_g2[i].addr.GetPort() == rmt_xrf_port) { /* inform XRF about the source */ - readBuffer2[11] = to_remote_g2[i].from_mod; + dsvt.flagb[2] = to_remote_g2[i].from_mod; - sendto(xrf_g2_sock, readBuffer2, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port)) { - readBuffer[0] = (unsigned char)(29 & 0xFF); - readBuffer[1] = (unsigned char)(29 >> 8 & 0x1F); - readBuffer[1] = (unsigned char)(readBuffer[1] | 0xFFFFFF80); + sendto(xrf_g2_sock, dsvt.title, 27, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); + } else if (to_remote_g2[i].addr.GetPort() == rmt_ref_port) { + SREFDSVT rdsvt; + rdsvt.head[0] = (unsigned char)(29 & 0xFF); + rdsvt.head[1] = (unsigned char)(29 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); - memcpy(readBuffer + 2, readBuffer2, 27); + memcpy(rdsvt.dsvt.title, dsvt.title, 27); - sendto(ref_g2_sock, readBuffer, 29, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) { + sendto(ref_g2_sock, rdsvt.head, 29, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); + } else if (to_remote_g2[i].addr.GetPort() == rmt_dcs_port) { memset(dcs_buf, 0x00, 600); dcs_buf[0] = dcs_buf[1] = dcs_buf[2] = '0'; dcs_buf[3] = '1'; - dcs_buf[4] = dcs_buf[5] = dcs_buf[6] = 0x00; - memcpy(dcs_buf + 7, to_remote_g2[i].to_call, 8); + dcs_buf[4] = dcs_buf[5] = dcs_buf[6] = 0x0; + memcpy(dcs_buf + 7, to_remote_g2[i].cs, 8); dcs_buf[14] = to_remote_g2[i].to_mod; memcpy(dcs_buf + 15, owner.c_str(), CALL_SIZE); dcs_buf[22] = to_remote_g2[i].from_mod; memcpy(dcs_buf + 23, "CQCQCQ ", 8); memcpy(dcs_buf + 31, xrf_2_dcs[i].mycall, 8); memcpy(dcs_buf + 39, xrf_2_dcs[i].sfx, 4); - dcs_buf[43] = readBuffer2[12]; /* streamid0 */ - dcs_buf[44] = readBuffer2[13]; /* streamid1 */ - dcs_buf[45] = readBuffer2[14]; /* cycle sequence */ - memcpy(dcs_buf + 46, readBuffer2 + 15, 12); + memcpy(dcs_buf + 43, &dsvt.streamid, 2); + dcs_buf[45] = dsvt.ctrl; /* cycle sequence */ + memcpy(dcs_buf + 46, dsvt.vasd.voice, 12); dcs_buf[58] = (xrf_2_dcs[i].dcs_rptr_seq >> 0) & 0xff; dcs_buf[59] = (xrf_2_dcs[i].dcs_rptr_seq >> 8) & 0xff; dcs_buf[60] = (xrf_2_dcs[i].dcs_rptr_seq >> 16) & 0xff; - xrf_2_dcs[i].dcs_rptr_seq ++; + xrf_2_dcs[i].dcs_rptr_seq++; dcs_buf[61] = 0x01; dcs_buf[62] = 0x00; - sendto(dcs_g2_sock, dcs_buf, 100, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); + sendto(dcs_g2_sock, dcs_buf, 100, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); } - if ((readBuffer2[14] & 0x40) != 0) { - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + if (dsvt.ctrl & 0x40) { + to_remote_g2[i].in_streamid = 0x0; } break; } @@ -2070,81 +1742,78 @@ static void runit() FD_CLR (xrf_g2_sock,&fdset); } - if (FD_ISSET(ref_g2_sock, &fdset)) { - fromlen = sizeof(struct sockaddr_in); - recvlen2 = recvfrom(ref_g2_sock, (char *)readBuffer2, 100, 0, (struct sockaddr *)&fromDst4,&fromlen); + if (keep_running && FD_ISSET(ref_g2_sock, &fdset)) { + socklen_t fromlen = sizeof(struct sockaddr_in); + unsigned char buf[100]; + int length = recvfrom(ref_g2_sock, buf, 100, 0, (struct sockaddr *)&fromDst4,&fromlen); - strncpy(ip, inet_ntoa(fromDst4.sin_addr),IP_SIZE); - ip[IP_SIZE] = '\0'; + strncpy(ip, fromDst4.GetAddress(), INET6_ADDRSTRLEN+1); + ip[INET_ADDRSTRLEN] = '\0'; found = false; /* LH */ - if ((recvlen2 == 4) && - (readBuffer2[0] == 4) && - (readBuffer2[1] == 192) && - (readBuffer2[2] == 7) && - (readBuffer2[3] == 0)) { + if (length==4 && buf[0]==4 && buf[1]==192 && buf[2]==7 && buf[3]==0) { unsigned short j_idx = 0; unsigned short k_idx = 0; unsigned char tmp[2]; auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - inbound_ptr = (inbound *)pos->second; + //SINBOUND *inbound = (SINBOUND *)pos->second; // printf("Remote station %s %s requested LH list\n", inbound_ptr->call, ip); /* header is 10 bytes */ /* reply type */ - readBuffer2[2] = 7; - readBuffer2[3] = 0; + buf[2] = 7; + buf[3] = 0; /* it looks like time_t here */ time(&tnow); - memcpy((char *)readBuffer2 + 6, (char *)&tnow, sizeof(time_t)); + memcpy(buf + 6, (char *)&tnow, sizeof(time_t)); for (auto r_dt_lh_pos = dt_lh_list.rbegin(); r_dt_lh_pos != dt_lh_list.rend(); r_dt_lh_pos++) { /* each entry has 24 bytes */ /* start at position 10 to bypass the header */ - strcpy((char *)readBuffer2 + 10 + (24 * j_idx), r_dt_lh_pos->second.c_str()); + strcpy((char *)buf + 10 + (24 * j_idx), r_dt_lh_pos->second.c_str()); p = strchr((char *)r_dt_lh_pos->first.c_str(), '='); if (p) { - memcpy((char *)readBuffer2 + 18 + (24 * j_idx), p + 2, 8); + memcpy((char *)buf + 18 + (24 * j_idx), p + 2, 8); /* if local or local w/gps */ - if ((p[1] == 'l') || (p[1] == 'g')) - readBuffer2[18 + (24 * j_idx) + 6] = *(p + 1); + if (p[1]=='l' || p[1]=='g') + buf[18 + (24 * j_idx) + 6] = *(p + 1); *p = '\0'; tnow = atol(r_dt_lh_pos->first.c_str()); *p = '='; - memcpy((char *)readBuffer2 + 26 + (24 * j_idx), &tnow, sizeof(time_t)); + memcpy(buf + 26 + (24 * j_idx), &tnow, sizeof(time_t)); } else { - memcpy((char *)readBuffer2 + 18 + (24 * j_idx), "ERROR ", 8); + memcpy(buf + 18 + (24 * j_idx), "ERROR ", 8); time(&tnow); - memcpy((char *)readBuffer2 + 26 + (24 * j_idx), &tnow, sizeof(time_t)); + memcpy(buf + 26 + (24 * j_idx), &tnow, sizeof(time_t)); } - readBuffer2[30 + (24 * j_idx)] = 0; - readBuffer2[31 + (24 * j_idx)] = 0; - readBuffer2[32 + (24 * j_idx)] = 0; - readBuffer2[33 + (24 * j_idx)] = 0; + buf[30 + (24 * j_idx)] = 0; + buf[31 + (24 * j_idx)] = 0; + buf[32 + (24 * j_idx)] = 0; + buf[33 + (24 * j_idx)] = 0; j_idx++; /* process 39 entries at a time */ if (j_idx == 39) { /* 39 * 24 = 936 + 10 header = 946 */ - readBuffer2[0] = 0xb2; - readBuffer2[1] = 0xc3; + buf[0] = 0xb2; + buf[1] = 0xc3; /* 39 entries */ - readBuffer2[4] = 0x27; - readBuffer2[5] = 0x00; + buf[4] = 0x27; + buf[5] = 0x00; - sendto(ref_g2_sock, readBuffer2, 946, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, buf, 946, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); j_idx = 0; } @@ -2153,22 +1822,20 @@ static void runit() if (j_idx != 0) { k_idx = 10 + (j_idx * 24); memcpy(tmp, (char *)&k_idx, 2); - readBuffer2[0] = tmp[0]; - readBuffer2[1] = tmp[1] | 0xc0; + buf[0] = tmp[0]; + buf[1] = tmp[1] | 0xc0; memcpy(tmp, (char *)&j_idx, 2); - readBuffer2[4] = tmp[0]; - readBuffer2[5] = tmp[1]; + buf[4] = tmp[0]; + buf[5] = tmp[1]; - sendto(ref_g2_sock, readBuffer2, k_idx, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, buf, k_idx, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); } } /* linked repeaters request */ - } else if ((recvlen2 == 4) && - (readBuffer2[0] == 4) && - (readBuffer2[1] == 192) && - (readBuffer2[2] == 5) && - (readBuffer2[3] == 0)) { + } else if (length==4 && buf[0]==4 && buf[1]==192 && buf[2]==5 && buf[3]==0) { + if (log_debug) + printf("Got a linked repeater request!\n"); unsigned short i_idx = 0; unsigned short j_idx = 0; unsigned short k_idx = 0; @@ -2177,60 +1844,57 @@ static void runit() auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - inbound_ptr = (inbound *)pos->second; + //SINBOUND *inbound = (SINBOUND *)pos->second; // printf("Remote station %s %s requested linked repeaters list\n", inbound_ptr->call, ip); /* header is 8 bytes */ /* reply type */ - readBuffer2[2] = 5; - readBuffer2[3] = 1; + buf[2] = 5; + buf[3] = 1; /* we can have up to 3 linked systems */ total = 3; memcpy(tmp, (char *)&total, 2); - readBuffer2[6] = tmp[0]; - readBuffer2[7] = tmp[1]; + buf[6] = tmp[0]; + buf[7] = tmp[1]; - for (i = 0, i_idx = 0; i < 3; i++, i_idx++) { + for (int i=0, i_idx=0; i<3; i++, i_idx++) { /* each entry has 20 bytes */ if (to_remote_g2[i].to_mod != ' ') { if (i == 0) - readBuffer2[8 + (20 * j_idx)] = 'A'; + buf[8 + (20 * j_idx)] = 'A'; else if (i == 1) - readBuffer2[8 + (20 * j_idx)] = 'B'; + buf[8 + (20 * j_idx)] = 'B'; else if (i == 2) - readBuffer2[8 + (20 * j_idx)] = 'C'; - - strcpy((char *)readBuffer2 + 9 + (20 * j_idx), to_remote_g2[i].to_call); - readBuffer2[16 + (20 * j_idx)] = to_remote_g2[i].to_mod; - - readBuffer2[17 + (20 * j_idx)] = 0; - readBuffer2[18 + (20 * j_idx)] = 0; - readBuffer2[19 + (20 * j_idx)] = 0; - readBuffer2[20 + (20 * j_idx)] = 0x50; - readBuffer2[21 + (20 * j_idx)] = 0x04; - readBuffer2[22 + (20 * j_idx)] = 0x32; - readBuffer2[23 + (20 * j_idx)] = 0x4d; - readBuffer2[24 + (20 * j_idx)] = 0x9f; - readBuffer2[25 + (20 * j_idx)] = 0xdb; - readBuffer2[26 + (20 * j_idx)] = 0x0e; - readBuffer2[27 + (20 * j_idx)] = 0; + buf[8 + (20 * j_idx)] = 'C'; + + strcpy((char *)buf + 9 + (20 * j_idx), to_remote_g2[i].cs); + buf[16 + (20 * j_idx)] = to_remote_g2[i].to_mod; + + buf[17 + (20 * j_idx)] = buf[18 + (20 * j_idx)] = buf[19 + (20 * j_idx)] = 0; + buf[20 + (20 * j_idx)] = 0x50; + buf[21 + (20 * j_idx)] = 0x04; + buf[22 + (20 * j_idx)] = 0x32; + buf[23 + (20 * j_idx)] = 0x4d; + buf[24 + (20 * j_idx)] = 0x9f; + buf[25 + (20 * j_idx)] = 0xdb; + buf[26 + (20 * j_idx)] = 0x0e; + buf[27 + (20 * j_idx)] = 0; j_idx++; if (j_idx == 39) { /* 20 bytes for each user, so 39 * 20 = 780 bytes + 8 bytes header = 788 */ - readBuffer2[0] = 0x14; - readBuffer2[1] = 0xc3; + buf[0] = 0x14; + buf[1] = 0xc3; k_idx = i_idx - 38; memcpy(tmp, (char *)&k_idx, 2); - readBuffer2[4] = tmp[0]; - readBuffer2[5] = tmp[1]; - - sendto(ref_g2_sock, readBuffer2,788,0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + buf[4] = tmp[0]; + buf[5] = tmp[1]; + sendto(ref_g2_sock, buf, 788, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); j_idx = 0; } } @@ -2239,8 +1903,8 @@ static void runit() if (j_idx != 0) { k_idx = 8 + (j_idx * 20); memcpy(tmp, (char *)&k_idx, 2); - readBuffer2[0] = tmp[0]; - readBuffer2[1] = tmp[1] | 0xc0; + buf[0] = tmp[0]; + buf[1] = tmp[1] | 0xc0; if (i_idx > j_idx) k_idx = i_idx - j_idx; @@ -2248,18 +1912,16 @@ static void runit() k_idx = 0; memcpy(tmp, (char *)&k_idx, 2); - readBuffer2[4] = tmp[0]; - readBuffer2[5] = tmp[1]; + buf[4] = tmp[0]; + buf[5] = tmp[1]; - sendto(ref_g2_sock, readBuffer2, 8+(j_idx*20), 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, buf, 8+(j_idx*20), 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); } } /* connected user list request */ - } else if ((recvlen2 == 4) && - (readBuffer2[0] == 4) && - (readBuffer2[1] == 192) && - (readBuffer2[2] == 6) && - (readBuffer2[3] == 0)) { + } else if (length==4 && buf[0]==4 && buf[1]==192 && buf[2]==6 && buf[3]==0) { + if (log_debug) + printf("Got a linked dongle request!!\n"); unsigned short i_idx = 0; unsigned short j_idx = 0; unsigned short k_idx = 0; @@ -2268,55 +1930,52 @@ static void runit() auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - inbound_ptr = (inbound *)pos->second; // printf("Remote station %s %s requested connected user list\n", inbound_ptr->call, ip); - /* header is 8 bytes */ - /* reply type */ - readBuffer2[2] = 6; - readBuffer2[3] = 0; + buf[2] = 6; + buf[3] = 0; /* total connected users */ total = inbound_list.size(); memcpy(tmp, (char *)&total, 2); - readBuffer2[6] = tmp[0]; - readBuffer2[7] = tmp[1]; + buf[6] = tmp[0]; + buf[7] = tmp[1]; for (pos = inbound_list.begin(), i_idx = 0; pos != inbound_list.end(); pos++, i_idx++) { /* each entry has 20 bytes */ - readBuffer2[8 + (20 * j_idx)] = ' '; - inbound_ptr = (inbound *)pos->second; + buf[8 + (20 * j_idx)] = ' '; + SINBOUND *inbound = (SINBOUND *)pos->second; - readBuffer2[8 + (20 * j_idx)] = inbound_ptr->mod; - strcpy((char *)readBuffer2 + 9 + (20 * j_idx), inbound_ptr->call); + buf[8 + (20 * j_idx)] = inbound->mod; + strcpy((char *)buf + 9 + (20 * j_idx), inbound->call); - readBuffer2[17 + (20 * j_idx)] = 0; + buf[17 + (20 * j_idx)] = 0; /* readBuffer2[18 + (20 * j_idx)] = 0; */ - readBuffer2[18 + (20 * j_idx)] = inbound_ptr->client; - readBuffer2[19 + (20 * j_idx)] = 0; - readBuffer2[20 + (20 * j_idx)] = 0x0d; - readBuffer2[21 + (20 * j_idx)] = 0x4d; - readBuffer2[22 + (20 * j_idx)] = 0x37; - readBuffer2[23 + (20 * j_idx)] = 0x4d; - readBuffer2[24 + (20 * j_idx)] = 0x6f; - readBuffer2[25 + (20 * j_idx)] = 0x98; - readBuffer2[26 + (20 * j_idx)] = 0x04; - readBuffer2[27 + (20 * j_idx)] = 0; + buf[18 + (20 * j_idx)] = inbound->client; + buf[19 + (20 * j_idx)] = 0; + buf[20 + (20 * j_idx)] = 0x0d; + buf[21 + (20 * j_idx)] = 0x4d; + buf[22 + (20 * j_idx)] = 0x37; + buf[23 + (20 * j_idx)] = 0x4d; + buf[24 + (20 * j_idx)] = 0x6f; + buf[25 + (20 * j_idx)] = 0x98; + buf[26 + (20 * j_idx)] = 0x04; + buf[27 + (20 * j_idx)] = 0; j_idx++; if (j_idx == 39) { /* 20 bytes for each user, so 39 * 20 = 788 bytes + 8 bytes header = 788 */ - readBuffer2[0] = 0x14; - readBuffer2[1] = 0xc3; + buf[0] = 0x14; + buf[1] = 0xc3; k_idx = i_idx - 38; memcpy(tmp, (char *)&k_idx, 2); - readBuffer2[4] = tmp[0]; - readBuffer2[5] = tmp[1]; + buf[4] = tmp[0]; + buf[5] = tmp[1]; - sendto(ref_g2_sock, readBuffer2, 788, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, buf, 788, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); j_idx = 0; } @@ -2325,8 +1984,8 @@ static void runit() if (j_idx != 0) { k_idx = 8 + (j_idx * 20); memcpy(tmp, (char *)&k_idx, 2); - readBuffer2[0] = tmp[0]; - readBuffer2[1] = tmp[1] | 0xc0; + buf[0] = tmp[0]; + buf[1] = tmp[1] | 0xc0; if (i_idx > j_idx) k_idx = i_idx - j_idx; @@ -2334,95 +1993,78 @@ static void runit() k_idx = 0; memcpy(tmp, (char *)&k_idx, 2); - readBuffer2[4] = tmp[0]; - readBuffer2[5] = tmp[1]; + buf[4] = tmp[0]; + buf[5] = tmp[1]; - sendto(ref_g2_sock, readBuffer2, 8+(j_idx*20), 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, buf, 8+(j_idx*20), 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); } } /* date request */ - } else if ((recvlen2 == 4) && - (readBuffer2[0] == 4) && - (readBuffer2[1] == 192) && - (readBuffer2[2] == 8) && - (readBuffer2[3] == 0)) { + } else if (length== 4 && buf[0]==4 && buf[1]==192 && buf[2]==8 && buf[3]==0) { + if (log_debug) + printf("Got a dongle time request!!\n"); time_t ltime; struct tm tm; auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - inbound_ptr = (inbound *)pos->second; + //SINBOUND *inbound = (SINBOUND *)pos->second; // printf("Remote station %s %s requested date\n", inbound_ptr->call, ip); time(<ime); localtime_r(<ime,&tm); - readBuffer2[0] = 34; - readBuffer2[1] = 192; - readBuffer2[2] = 8; - readBuffer2[3] = 0; - readBuffer2[4] = 0xb5; - readBuffer2[5] = 0xae; - readBuffer2[6] = 0x37; - readBuffer2[7] = 0x4d; - snprintf((char *)readBuffer2 + 8, 1024 - 1, - "20%02d/%02d/%02d %02d:%02d:%02d %5.5s", - tm.tm_year % 100, tm.tm_mon+1,tm.tm_mday, - tm.tm_hour,tm.tm_min,tm.tm_sec, - (tzname[0] == NULL)?" ":tzname[0]); - - sendto(ref_g2_sock, readBuffer2, 34, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + buf[0] = 34; + buf[4] = 0xb5; + buf[5] = 0xae; + buf[6] = 0x37; + buf[7] = 0x4d; + snprintf((char *)buf + 8, 99, "20%02d/%02d/%02d %02d:%02d:%02d %5.5s", + tm.tm_year % 100, tm.tm_mon+1,tm.tm_mday, tm.tm_hour,tm.tm_min,tm.tm_sec, + (tzname[0] == NULL)?" ":tzname[0]); + + sendto(ref_g2_sock, buf, 34, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); } /* version request */ - } else if ((recvlen2 == 4) && - (readBuffer2[0] == 4) && - (readBuffer2[1] == 192) && - (readBuffer2[2] == 3) && - (readBuffer2[3] == 0)) { + } else if (length== 4 && buf[0]==4 && buf[1]==192 && buf[2]==3 && buf[3]==0) { + if (log_debug) + printf("Got a version request!!\n"); auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - inbound_ptr = (inbound *)pos->second; + //SINBOUND *inbound = (SINBOUND *)pos->second; // printf("Remote station %s %s requested version\n", inbound_ptr->call, ip); - readBuffer2[0] = 9; - readBuffer2[1] = 192; - readBuffer2[2] = 3; - readBuffer2[3] = 0; - strncpy((char *)readBuffer2 + 4, VERSION, 4); - readBuffer2[8] = 0; + buf[0] = 9; + memcpy((char *)buf + 4, "1.00", 4); + buf[8] = 0; - sendto(ref_g2_sock, readBuffer2, 9, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, buf, 9, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); } - } else if ((recvlen2 == 5) && - (readBuffer2[0] == 5) && - (readBuffer2[1] == 0) && - (readBuffer2[2] == 24) && - (readBuffer2[3] == 0) && - (readBuffer2[4] == 0)) { + } + else if (length==5 && buf[0]==5 && buf[1]==0 && buf[2]==24 && buf[3]==0 && buf[4]==0) { + if (log_debug) + printf("Got a disconnect request!!\n"); /* reply with the same DISCONNECT */ - sendto(ref_g2_sock, readBuffer2, 5, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, buf, 5, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); - for (i = 0; i < 3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { - printf("Call %s disconnected\n", to_remote_g2[i].to_call); + for (int i=0; i<3; i++) { + if (fromDst4==to_remote_g2[i].addr && to_remote_g2[i].addr.GetPort()==rmt_ref_port) { + printf("Call %s disconnected\n", to_remote_g2[i].cs); - to_remote_g2[i].to_call[0] = '\0'; - memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; + to_remote_g2[i].cs[0] = '\0'; + to_remote_g2[i].addr.Clear(); + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; } } auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - inbound_ptr = (inbound *)pos->second; - if (memcmp(inbound_ptr->call, "1NFO", 4) != 0) - printf("Call %s disconnected\n", inbound_ptr->call); + SINBOUND *inbound = (SINBOUND *)pos->second; + if (memcmp(inbound->call, "1NFO", 4) != 0) + printf("Call %s disconnected\n", inbound->call); free(pos->second); pos->second = NULL; inbound_list.erase(pos); @@ -2430,24 +2072,18 @@ static void runit() print_status_file(); } - for (i = 0; i < 3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { + for (int i=0; i<3; i++) { + if (fromDst4==to_remote_g2[i].addr && to_remote_g2[i].addr.GetPort()==rmt_ref_port) { found = true; - if ((recvlen2 == 5) && - (readBuffer2[0] == 5) && - (readBuffer2[1] == 0) && - (readBuffer2[2] == 24) && - (readBuffer2[3] == 0) && - (readBuffer2[4] == 1)) { - printf("Connected to call %s\n", to_remote_g2[i].to_call); + if (length==5 && buf[0]==5 && buf[1]==0 && buf[2]==24 && buf[3]==0 && buf[4]==1) { + printf("Connected to call %s\n", to_remote_g2[i].cs); queryCommand[0] = 28; queryCommand[1] = 192; queryCommand[2] = 4; queryCommand[3] = 0; memcpy(queryCommand + 4, login_call.c_str(), CALL_SIZE); - for (j = 11; j > 3; j--) { + for (int j=11; j>3; j--) { if (queryCommand[j] == ' ') queryCommand[j] = '\0'; else @@ -2458,107 +2094,72 @@ static void runit() // ATTENTION: I should ONLY send once for each distinct // remote IP, so get out of the loop immediately - sendto(ref_g2_sock, queryCommand,28,0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); + sendto(ref_g2_sock, queryCommand, 28, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); break; } } } - for (i = 0; i < 3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { + for (int i=0; i<3; i++) { + if ((fromDst4==to_remote_g2[i].addr) && (to_remote_g2[i].addr.GetPort()==rmt_ref_port)) { found = true; - if ((recvlen2 == 8) && - (readBuffer2[0] == 8) && - (readBuffer2[1] == 192) && - (readBuffer2[2] == 4) && - (readBuffer2[3] == 0)) { - if ((readBuffer2[4] == 79) && - (readBuffer2[5] == 75) && - (readBuffer2[6] == 82)) { + if (length==8 && buf[0]==8 && buf[1]==192 && buf[2]==4 && buf[3]==0) { + if (buf[4]== 79 && buf[5]==75 && buf[6]==82) { if (!to_remote_g2[i].is_connected) { to_remote_g2[i].is_connected = true; to_remote_g2[i].countdown = TIMEOUT; - printf("Login OK to call %s mod %c\n", - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); + printf("Login OK to call %s mod %c\n", to_remote_g2[i].cs, to_remote_g2[i].to_mod); print_status_file(); tracing[i].last_time = time(NULL); - strcpy(linked_remote_system, to_remote_g2[i].to_call); + strcpy(linked_remote_system, to_remote_g2[i].cs); space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", - to_remote_g2[i].from_mod, - linked_remote_system, - to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); } - } else if ((readBuffer2[4] == 70) && - (readBuffer2[5] == 65) && - (readBuffer2[6] == 73) && - (readBuffer2[7] == 76)) { - printf("Login failed to call %s mod %c\n", - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - - sprintf(notify_msg, "%c_failed_linked.dat_FAILED_TO_LINK", - to_remote_g2[i].from_mod); - audio_notify(notify_msg); - - to_remote_g2[i].to_call[0] = '\0'; - memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; + } else if (buf[4]==70 && buf[5]==65 && buf[6]==73 && buf[7]==76) { + printf("Login failed to call %s mod %c\n", to_remote_g2[i].cs, to_remote_g2[i].to_mod); + + sprintf(notify_msg[i], "%c_failed_link.dat_FAILED_TO_LINK", to_remote_g2[i].from_mod); + + to_remote_g2[i].cs[0] = '\0'; + to_remote_g2[i].addr.Clear(); + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; - } else if ((readBuffer2[4] == 66) && - (readBuffer2[5] == 85) && - (readBuffer2[6] == 83) && - (readBuffer2[7] == 89)) { - printf("Busy or unknown status from call %s mod %c\n", - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - - sprintf(notify_msg, "%c_failed_linked.dat_FAILED_TO_LINK", - to_remote_g2[i].from_mod); - audio_notify(notify_msg); - - to_remote_g2[i].to_call[0] = '\0'; - memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; + to_remote_g2[i].in_streamid = 0x0; + } else if (buf[4]==66 && buf[5]==85 && buf[6]==83 && buf[7]==89) { + printf("Busy or unknown status from call %s mod %c\n", to_remote_g2[i].cs, to_remote_g2[i].to_mod); + + sprintf(notify_msg[i], "%c_failed_link.dat_FAILED_TO_LINK", to_remote_g2[i].from_mod); + + to_remote_g2[i].cs[0] = '\0'; + to_remote_g2[i].addr.Clear(); + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; } } } } - for (i = 0; i < 3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { + for (int i=0; i<3; i++) { + if ((fromDst4==to_remote_g2[i].addr) && (to_remote_g2[i].addr.GetPort()==rmt_ref_port)) { found = true; - if ((recvlen2 == 24) && - (readBuffer2[0] == 24) && - (readBuffer2[1] == 192) && - (readBuffer2[2] == 3) && - (readBuffer2[3] == 0)) { - j = i; + if (length==24 && buf[0]==24 && buf[1]==192 && buf[2]==3 && buf[3]==0) { to_remote_g2[i].countdown = TIMEOUT; } } } - for (i = 0; i < 3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { + for (int i=0; i<3; i++) { + if (fromDst4==to_remote_g2[i].addr && to_remote_g2[i].addr.GetPort()==rmt_ref_port) { found = true; - if (recvlen2 == 3) + if (length == 3) to_remote_g2[i].countdown = TIMEOUT; } } @@ -2566,9 +2167,9 @@ static void runit() /* find out if it is a connected dongle */ auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - inbound_ptr = (inbound *)pos->second; + SINBOUND *inbound = (SINBOUND *)pos->second; found = true; - inbound_ptr->countdown = TIMEOUT; + inbound->countdown = TIMEOUT; /*** ip is same, do not update port memcpy((char *)&(inbound_ptr->sin),(char *)&fromDst4, sizeof(struct sockaddr_in)); ***/ @@ -2580,121 +2181,103 @@ static void runit() and it is not a connected dongle. In this case, this must be an INCOMING dongle request */ - if ((recvlen2 == 5) && - (readBuffer2[0] == 5) && - (readBuffer2[1] == 0) && - (readBuffer2[2] == 24) && - (readBuffer2[3] == 0) && - (readBuffer2[4] == 1)) { + if (length==5 && buf[0]==5 && buf[1]==0 && buf[2]==24 && buf[3]==0 && buf[4]==1) { if ((inbound_list.size() + 1) > max_dongles) printf("Inbound DONGLE-p connection from %s but over the max_dongles limit of %d\n", ip, (int)inbound_list.size()); else - sendto(ref_g2_sock, readBuffer2, 5, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); - } else if ((recvlen2 == 28) && - (readBuffer2[0] == 28) && - (readBuffer2[1] == 192) && - (readBuffer2[2] == 4) && - (readBuffer2[3] == 0)) { + sendto(ref_g2_sock, buf, 5, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); + } else if (length==28 && buf[0]==28 && buf[1]==192 && buf[2]==4 && buf[3]==0) { /* verify callsign */ - memcpy(call, readBuffer2 + 4, CALL_SIZE); + memcpy(call, buf + 4, CALL_SIZE); call[CALL_SIZE] = '\0'; - for (i = 7; i > 0; i--) { + for (int i=7; i>0; i--) { if (call[i] == '\0') call[i] = ' '; else break; } - if (memcmp(call, "1NFO", 4) != 0) - printf("Inbound DONGLE-p CALL=%s, ip=%s, DV=%.8s\n", - call, ip, readBuffer2 + 20); + if (memcmp(call, "1NFO", 4)) + printf("Inbound DONGLE-p CALL=%s, ip=%s, DV=%.8s\n", call, ip, buf + 20); if ((inbound_list.size() + 1) > max_dongles) printf("Inbound DONGLE-p connection from %s but over the max_dongles limit of %d\n", ip, (int)inbound_list.size()); - else if (admin.size() && (admin.find(call) == admin.end())) - printf("Incoming call [%s] from %s not an ADMIN\n", call, ip); + //else if (admin.size() && (admin.find(call) == admin.end())) + // printf("Incoming call [%s] from %s not an ADMIN\n", call, ip); else if (regexec(&preg, call, 0, NULL, 0) != 0) { printf("Invalid dongle callsign: CALL=%s,ip=%s\n", call, ip); - readBuffer2[0] = 8; - readBuffer2[4] = 70; - readBuffer2[5] = 65; - readBuffer2[6] = 73; - readBuffer2[7] = 76; + buf[0] = 8; + buf[4] = 70; + buf[5] = 65; + buf[6] = 73; + buf[7] = 76; - sendto(ref_g2_sock, readBuffer2, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); + sendto(ref_g2_sock, buf, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); } else { /* add the dongle to the inbound list */ - inbound_ptr = (inbound *)malloc(sizeof(inbound)); - if (inbound_ptr) { - inbound_ptr->countdown = TIMEOUT; - memcpy((char *)&(inbound_ptr->sin),(char *)&fromDst4, sizeof(struct sockaddr_in)); - strcpy(inbound_ptr->call, call); - - inbound_ptr->mod = ' '; - - if (memcmp(readBuffer2 + 20, "AP", 2) == 0) - inbound_ptr->client = 'A'; /* dvap */ - else if (memcmp(readBuffer2 + 20, "DV019999", 8) == 0) - inbound_ptr->client = 'H'; /* spot */ + SINBOUND *inbound = (SINBOUND *)malloc(sizeof(SINBOUND)); + if (inbound) { + inbound->countdown = TIMEOUT; + inbound->addr = fromDst4; + strcpy(inbound->call, call); + + inbound->mod = ' '; + + if (memcmp(buf + 20, "AP", 2) == 0) + inbound->client = 'A'; /* dvap */ + else if (memcmp(buf + 20, "DV019999", 8) == 0) + inbound->client = 'H'; /* spot */ else - inbound_ptr->client = 'D'; /* dongle */ + inbound->client = 'D'; /* dongle */ - auto insert_pair = inbound_list.insert(std::pair(ip, inbound_ptr)); + auto insert_pair = inbound_list.insert(std::pair(ip, inbound)); if (insert_pair.second) { - if (memcmp(inbound_ptr->call, "1NFO", 4) != 0) - printf("new CALL=%s, DONGLE-p, ip=%s, users=%d\n", inbound_ptr->call,ip, (int)inbound_list.size()); + if (memcmp(inbound->call, "1NFO", 4) != 0) + printf("new CALL=%s, DONGLE-p, ip=%s, users=%d\n", inbound->call,ip, (int)inbound_list.size()); - readBuffer2[0] = 8; - readBuffer2[4] = 79; - readBuffer2[5] = 75; - readBuffer2[6] = 82; - readBuffer2[7] = 87; - - sendto(ref_g2_sock, readBuffer2, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); + buf[0] = 8; + buf[4] = 79; + buf[5] = 75; + buf[6] = 82; + buf[7] = 87; + sendto(ref_g2_sock, buf, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); print_status_file(); } else { - printf("failed to add CALL=%s,ip=%s\n",inbound_ptr->call,ip); - free(inbound_ptr); - inbound_ptr = NULL; + printf("failed to add CALL=%s,ip=%s\n",inbound->call,ip); + free(inbound); + inbound = NULL; - readBuffer2[0] = 8; - readBuffer2[4] = 70; - readBuffer2[5] = 65; - readBuffer2[6] = 73; - readBuffer2[7] = 76; + buf[0] = 8; + buf[4] = 70; + buf[5] = 65; + buf[6] = 73; + buf[7] = 76; - sendto(ref_g2_sock, readBuffer2, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); + sendto(ref_g2_sock, buf, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); } } else { printf("malloc() failed for call=%s,ip=%s\n",call,ip); - readBuffer2[0] = 8; - readBuffer2[4] = 70; - readBuffer2[5] = 65; - readBuffer2[6] = 73; - readBuffer2[7] = 76; + buf[0] = 8; + buf[4] = 70; + buf[5] = 65; + buf[6] = 73; + buf[7] = 76; - sendto(ref_g2_sock, readBuffer2, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); + sendto(ref_g2_sock, buf, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); } } } } - if ( ((recvlen2 == 58) || - (recvlen2 == 29) || - (recvlen2 == 32)) && - (memcmp(readBuffer2 + 2, "DSVT", 4) == 0) && - ((readBuffer2[6] == 0x10) || - (readBuffer2[6] == 0x20)) && - (readBuffer2[10] == 0x20)) { + if ((length==58 || length==29 || length==32) && 0==memcmp(buf + 2, "DSVT", 4) && (buf[6]==0x10 || buf[6]==0x20) && buf[10]==0x20) { /* Is it one of the donglers or repeaters-reflectors */ found = false; - for (i = 0; i < 3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { + for (int i=0; i<3; i++) { + if (fromDst4==to_remote_g2[i].addr && to_remote_g2[i].addr.GetPort()==rmt_ref_port) { to_remote_g2[i].countdown = TIMEOUT; found = true; } @@ -2702,45 +2285,39 @@ static void runit() if (!found) { auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - inbound_ptr = (inbound *)pos->second; - inbound_ptr->countdown = TIMEOUT; + SINBOUND *inbound = (SINBOUND *)pos->second; + inbound->countdown = TIMEOUT; found = true; } } - if ((recvlen2 == 58) && found) { + SREFDSVT rdsvt; memcpy(rdsvt.head, buf, length); // copy to struct + + if (length==58 && found) { memset(source_stn, ' ', 9); source_stn[8] = '\0'; /* some bad hotspot programs out there using INCORRECT flag */ - if (readBuffer2[17] == 0x40) - readBuffer2[17] = 0x00; - else if (readBuffer2[17] == 0x48) - readBuffer2[17] = 0x08; - else if (readBuffer2[17] == 0x60) - readBuffer2[17] = 0x20; - else if (readBuffer2[17] == 0x68) - readBuffer2[17] = 0x28; + if (rdsvt.dsvt.hdr.flag[0]==0x40U || rdsvt.dsvt.hdr.flag[0]==0x48U || rdsvt.dsvt.hdr.flag[0]==0x60U || rdsvt.dsvt.hdr.flag[0]==0x68U) + rdsvt.dsvt.hdr.flag[0] -= 0x40U; /* A reflector will send to us its own RPT1 */ /* A repeater will send to us its own RPT1 */ - /* A dongleR will send to us our RPT1 */ + /* A dongler will send to us our RPT1 */ /* It is from a repeater-reflector, correct rpt1, rpt2 and re-compute pfcs */ - for (i = 0; i < 3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port)) && + int i; + for (i=0; i<3; i++) { + if (fromDst4==to_remote_g2[i].addr && to_remote_g2[i].addr.GetPort()==rmt_ref_port && ( - ((memcmp(readBuffer2 + 20, to_remote_g2[i].to_call, 7) == 0) && - (readBuffer2[27] == to_remote_g2[i].to_mod)) || - ((memcmp(readBuffer2 + 28, to_remote_g2[i].to_call, 7) == 0) && - (readBuffer2[35] == to_remote_g2[i].to_mod)) + (0==memcmp(rdsvt.dsvt.hdr.rpt1, to_remote_g2[i].cs, 7) && rdsvt.dsvt.hdr.rpt1[7]==to_remote_g2[i].to_mod) || + (0==memcmp(rdsvt.dsvt.hdr.rpt2, to_remote_g2[i].cs, 7) && rdsvt.dsvt.hdr.rpt2[7]==to_remote_g2[i].to_mod) )) { - memcpy(&readBuffer2[20], owner.c_str(), CALL_SIZE); - readBuffer2[27] = to_remote_g2[i].from_mod; - memcpy(&readBuffer2[36], "CQCQCQ ", 8); + memcpy(rdsvt.dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE); + rdsvt.dsvt.hdr.rpt1[7] = to_remote_g2[i].from_mod; + memcpy(rdsvt.dsvt.hdr.urcall, "CQCQCQ ", CALL_SIZE); - memcpy(source_stn, to_remote_g2[i].to_call, 8); + memcpy(source_stn, to_remote_g2[i].cs, CALL_SIZE); source_stn[7] = to_remote_g2[i].to_mod; break; @@ -2750,42 +2327,39 @@ static void runit() if (i == 3) { pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - inbound_ptr = (inbound *)pos->second; - memcpy(source_stn, inbound_ptr->call, 8); + SINBOUND *inbound = (SINBOUND *)pos->second; + memcpy(source_stn, inbound->call, 8); } } /* somebody's crazy idea of having a personal callsign in RPT2 */ /* we must set it to our gateway callsign */ - memcpy(&readBuffer2[28], owner.c_str(), CALL_SIZE); - readBuffer2[35] = 'G'; - calcPFCS(readBuffer2 + 2,56); + memcpy(rdsvt.dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE); + rdsvt.dsvt.hdr.rpt2[7] = 'G'; + calcPFCS(rdsvt.dsvt.title, 56); /* At this point, all data have our RPT1 and RPT2 */ i = -1; - if (readBuffer2[27] == 'A') + if (rdsvt.dsvt.hdr.rpt1[7] == 'A') i = 0; - else if (readBuffer2[27] == 'B') + else if (rdsvt.dsvt.hdr.rpt1[7] == 'B') i = 1; - else if (readBuffer2[27] == 'C') + else if (rdsvt.dsvt.hdr.rpt1[7] == 'C') i = 2; /* are we sure that RPT1 is our system? */ - if ((memcmp(readBuffer2 + 20, owner.c_str(), CALL_SIZE-1) == 0) && (i >= 0)) { + if (0==memcmp(rdsvt.dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE-1) && i>=0) { /* Last Heard */ - if (memcmp(old_sid[i].sid, readBuffer2 + 14, 2) != 0) { + if (old_sid[i].sid != rdsvt.dsvt.streamid) { if (qso_details) - printf("START from remote g2: streamID=%d,%d, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s, source=%.8s\n", - readBuffer2[14],readBuffer2[15], - readBuffer2[17], readBuffer2[18], readBuffer2[19], - &readBuffer2[44], - &readBuffer2[52], &readBuffer2[36], - &readBuffer2[20], &readBuffer2[28], - recvlen2, inet_ntoa(fromDst4.sin_addr), source_stn); + printf("START from remote g2: streamID=%04x, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s, source=%.8s\n", + ntohs(rdsvt.dsvt.streamid), rdsvt.dsvt.hdr.flag[0], rdsvt.dsvt.hdr.flag[0], rdsvt.dsvt.hdr.flag[0], + rdsvt.dsvt.hdr.mycall, rdsvt.dsvt.hdr.sfx, rdsvt.dsvt.hdr.urcall, rdsvt.dsvt.hdr.rpt1, rdsvt.dsvt.hdr.rpt2, + length, fromDst4.GetAddress(), source_stn); // put user into tmp1 - memcpy(tmp1, readBuffer2 + 44, 8); + memcpy(tmp1, rdsvt.dsvt.hdr.mycall, 8); tmp1[8] = '\0'; // delete the user if exists @@ -2802,69 +2376,64 @@ static void runit() } // add user time(&tnow); - sprintf(tmp2, "%ld=r%.6s%c%c", tnow, source_stn, source_stn[7], readBuffer2[27]); + sprintf(tmp2, "%ld=r%.6s%c%c", tnow, source_stn, source_stn[7], rdsvt.dsvt.hdr.rpt1[7]); dt_lh_list[tmp2] = tmp1; - memcpy(old_sid[i].sid, readBuffer2 + 14, 2); + old_sid[i].sid = rdsvt.dsvt.streamid; } /* send the data to the local gateway/repeater */ - sendto(rptr_sock, readBuffer2+2, 56, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + Link2Gate.Write(rdsvt.dsvt.title, 56); /* send the data to the donglers */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; - if (fromDst4.sin_addr.s_addr != inbound_ptr->sin.sin_addr.s_addr) { - sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + SINBOUND *inbound = (SINBOUND *)pos->second; + if (! (fromDst4 == inbound->addr)) { + sendto(ref_g2_sock, rdsvt.head, 58, 0, inbound->addr.GetPointer(), inbound->addr.GetSize()); } else - inbound_ptr->mod = readBuffer2[27]; + inbound->mod = rdsvt.dsvt.hdr.rpt1[7]; } - if ((to_remote_g2[i].toDst4.sin_addr.s_addr != fromDst4.sin_addr.s_addr) && - to_remote_g2[i].is_connected) { + if ((! (to_remote_g2[i].addr==fromDst4)) && to_remote_g2[i].is_connected) { if ( /*** (memcmp(readBuffer2 + 44, owner, 8) != 0) && ***/ /* block repeater announcements */ - (memcmp(readBuffer2 + 36, "CQCQCQ", 6) == 0) && /* CQ calls only */ - ((readBuffer2[17] == 0x00) || /* normal */ - (readBuffer2[17] == 0x08) || /* EMR */ - (readBuffer2[17] == 0x20) || /* BK */ - (readBuffer2[17] == 0x28)) && /* EMR + BK */ - (memcmp(readBuffer2 + 28, owner.c_str(), CALL_SIZE-1) == 0) && /* rpt2 must be us */ - (readBuffer2[35] == 'G')) { - to_remote_g2[i].in_streamid[0] = readBuffer2[14]; - to_remote_g2[i].in_streamid[1] = readBuffer2[15]; - - if ((to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) || - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { - memcpy((char *)readBuffer2 + 20, to_remote_g2[i].to_call, CALL_SIZE); - readBuffer2[27] = to_remote_g2[i].to_mod; - memcpy((char *)readBuffer2 + 28, to_remote_g2[i].to_call, CALL_SIZE); - readBuffer2[35] = 'G'; - calcPFCS(readBuffer2 + 2, 56); - - if (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) { + 0==memcmp(rdsvt.dsvt.hdr.urcall, "CQCQCQ", 6) && /* CQ calls only */ + (rdsvt.dsvt.hdr.flag[0]==0x00 || /* normal */ + rdsvt.dsvt.hdr.flag[0]==0x08 || /* EMR */ + rdsvt.dsvt.hdr.flag[0]==0x20 || /* BK */ + rdsvt.dsvt.hdr.flag[7]==0x28) && /* EMR + BK */ + 0==memcmp(rdsvt.dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE-1) && /* rpt2 must be us */ + rdsvt.dsvt.hdr.rpt2[7] == 'G') { + to_remote_g2[i].in_streamid = rdsvt.dsvt.streamid; + + if (to_remote_g2[i].addr.GetPort()==rmt_xrf_port || to_remote_g2[i].addr.GetPort()==rmt_ref_port) { + memcpy(rdsvt.dsvt.hdr.rpt1, to_remote_g2[i].cs, CALL_SIZE); + rdsvt.dsvt.hdr.rpt1[7] = to_remote_g2[i].to_mod; + memcpy(rdsvt.dsvt.hdr.rpt2, to_remote_g2[i].cs, CALL_SIZE); + rdsvt.dsvt.hdr.rpt2[7] = 'G'; + calcPFCS(rdsvt.dsvt.title, 56); + + if (to_remote_g2[i].addr.GetPort() == rmt_xrf_port) { /* inform XRF about the source */ - readBuffer2[13] = to_remote_g2[i].from_mod; - - sendto(xrf_g2_sock, readBuffer2 + 2, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + rdsvt.dsvt.flagb[2] = to_remote_g2[i].from_mod; + sendto(xrf_g2_sock, rdsvt.dsvt.title, 56, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); } else - sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) { - memcpy(ref_2_dcs[i].mycall, readBuffer2 + 44, 8); - memcpy(ref_2_dcs[i].sfx, readBuffer2 + 52, 4); + sendto(ref_g2_sock, rdsvt.head, 58, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); + } else if (to_remote_g2[i].addr.GetPort() == rmt_dcs_port) { + memcpy(ref_2_dcs[i].mycall, rdsvt.dsvt.hdr.mycall, 8); + memcpy(ref_2_dcs[i].sfx, rdsvt.dsvt.hdr.sfx, 4); ref_2_dcs[i].dcs_rptr_seq = 0; } } } } } else if (found) { - if ((readBuffer2[16] & 0x40) != 0) { - for (i = 0; i < 3; i++) { - if (memcmp(old_sid[i].sid, readBuffer2 + 14, 2) == 0) { + if (rdsvt.dsvt.ctrl & 0x40U) { + for (int i=0; i<3; i++) { + if (old_sid[i].sid == rdsvt.dsvt.streamid) { if (qso_details) - printf("END from remote g2: streamID=%d,%d, %d bytes from IP=%s\n", - readBuffer2[14],readBuffer2[15],recvlen2,inet_ntoa(fromDst4.sin_addr)); + printf("END from remote g2: streamID=%04x, %d bytes from IP=%s\n", ntohs(rdsvt.dsvt.streamid), length, fromDst4.GetAddress()); - memset(old_sid[i].sid, 0x00, 2); + old_sid[i].sid = 0x0; break; } @@ -2872,43 +2441,40 @@ static void runit() } /* send the data to the local gateway/repeater */ - sendto(rptr_sock, readBuffer2+2, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + Link2Gate.Write(rdsvt.dsvt.title, 27); /* send the data to the donglers */ for (pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; - if (fromDst4.sin_addr.s_addr != inbound_ptr->sin.sin_addr.s_addr) { - sendto(ref_g2_sock, readBuffer2, 29, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + SINBOUND *inbound = (SINBOUND *)pos->second; + if (! (fromDst4 == inbound->addr)) { + sendto(ref_g2_sock, rdsvt.head, 29, 0, inbound->addr.GetPointer(), inbound->addr.GetSize()); } } - for (i = 0; i < 3; i++) { - if ((to_remote_g2[i].is_connected) && - (to_remote_g2[i].toDst4.sin_addr.s_addr != fromDst4.sin_addr.s_addr) && - (memcmp(to_remote_g2[i].in_streamid, readBuffer2 + 14, 2) == 0)) { - if (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) { + for (int i=0; i<3; i++) { + if (to_remote_g2[i].is_connected && (! (to_remote_g2[i].addr==fromDst4)) && to_remote_g2[i].in_streamid==rdsvt.dsvt.streamid) { + if (to_remote_g2[i].addr.GetPort() == rmt_xrf_port) { /* inform XRF about the source */ - readBuffer2[13] = to_remote_g2[i].from_mod; - - sendto(xrf_g2_sock, readBuffer2+2, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port)) - sendto(ref_g2_sock, readBuffer2, 29, 0,(struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) { + rdsvt.dsvt.flagb[2] = to_remote_g2[i].from_mod; + sendto(xrf_g2_sock, rdsvt.dsvt.title, 27, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); + } else if (to_remote_g2[i].addr.GetPort() == rmt_ref_port) + sendto(ref_g2_sock, rdsvt.head, 29, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); + else if (to_remote_g2[i].addr.GetPort() == rmt_dcs_port) { memset(dcs_buf, 0x00, 600); dcs_buf[0] = dcs_buf[1] = dcs_buf[2] = '0'; dcs_buf[3] = '1'; - dcs_buf[4] = dcs_buf[5] = dcs_buf[6] = 0x00; - memcpy(dcs_buf + 7, to_remote_g2[i].to_call, 8); + dcs_buf[4] = dcs_buf[5] = dcs_buf[6] = 0x0; + memcpy(dcs_buf + 7, to_remote_g2[i].cs, 8); dcs_buf[14] = to_remote_g2[i].to_mod; memcpy(dcs_buf + 15, owner.c_str(), CALL_SIZE); dcs_buf[22] = to_remote_g2[i].from_mod; memcpy(dcs_buf + 23, "CQCQCQ ", 8); memcpy(dcs_buf + 31, ref_2_dcs[i].mycall, 8); memcpy(dcs_buf + 39, ref_2_dcs[i].sfx, 4); - dcs_buf[43] = readBuffer2[14]; /* streamid0 */ - dcs_buf[44] = readBuffer2[15]; /* streamid1 */ - dcs_buf[45] = readBuffer2[16]; /* cycle sequence */ - memcpy(dcs_buf + 46, readBuffer2 + 17, 12); + dcs_buf[43] = buf[14]; /* streamid0 */ + dcs_buf[44] = buf[15]; /* streamid1 */ + dcs_buf[45] = buf[16]; /* cycle sequence */ + memcpy(dcs_buf + 46, rdsvt.dsvt.vasd.voice, 12); dcs_buf[58] = (ref_2_dcs[i].dcs_rptr_seq >> 0) & 0xff; dcs_buf[59] = (ref_2_dcs[i].dcs_rptr_seq >> 8) & 0xff; @@ -2919,12 +2485,11 @@ static void runit() dcs_buf[61] = 0x01; dcs_buf[62] = 0x00; - sendto(dcs_g2_sock, dcs_buf, 100, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); + sendto(dcs_g2_sock, dcs_buf, 100, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); } - if ((readBuffer2[16] & 0x40) != 0) { - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + if (rdsvt.dsvt.ctrl & 0x40) { + to_remote_g2[i].in_streamid = 0x0; } break; } @@ -2934,27 +2499,24 @@ static void runit() FD_CLR (ref_g2_sock,&fdset); } - if (FD_ISSET(dcs_g2_sock, &fdset)) { - fromlen = sizeof(struct sockaddr_in); - recvlen2 = recvfrom(dcs_g2_sock, (char *)dcs_buf, 1000, 0, (struct sockaddr *)&fromDst4, &fromlen); + if (keep_running && FD_ISSET(dcs_g2_sock, &fdset)) { + socklen_t fromlen = sizeof(struct sockaddr_in); + int length = recvfrom(dcs_g2_sock, dcs_buf, 1000, 0, (struct sockaddr *)&fromDst4, &fromlen); - strncpy(ip, inet_ntoa(fromDst4.sin_addr),IP_SIZE); - ip[IP_SIZE] = '\0'; + strncpy(ip, fromDst4.GetAddress(), INET6_ADDRSTRLEN); + ip[INET6_ADDRSTRLEN] = '\0'; /* header, audio */ - if ((dcs_buf[0] == '0') && (dcs_buf[1] == '0') && - (dcs_buf[2] == '0') && (dcs_buf[3] == '1')) { - if (recvlen2 == 100) { + if (dcs_buf[0]=='0' && dcs_buf[1]=='0' && dcs_buf[2]=='0' && dcs_buf[3]=='1') { + if (length == 100) { memset(source_stn, ' ', 9); source_stn[8] = '\0'; /* find out our local module */ - for (i = 0; i < 3; i++) { - if ((to_remote_g2[i].is_connected) && - (fromDst4.sin_addr.s_addr = to_remote_g2[i].toDst4.sin_addr.s_addr) && - (memcmp(dcs_buf + 7, to_remote_g2[i].to_call, 7) == 0) && - (to_remote_g2[i].to_mod == dcs_buf[14])) { - memcpy(source_stn, to_remote_g2[i].to_call, 8); + int i; + for (i=0; i<3; i++) { + if (to_remote_g2[i].is_connected && fromDst4==to_remote_g2[i].addr && 0==memcmp(dcs_buf + 7, to_remote_g2[i].cs, 7) && to_remote_g2[i].to_mod==dcs_buf[14]) { + memcpy(source_stn, to_remote_g2[i].cs, 8); source_stn[7] = to_remote_g2[i].to_mod; break; } @@ -2963,22 +2525,17 @@ static void runit() /* Is it our local module */ if (i < 3) { /* Last Heard */ - if (memcmp(old_sid[i].sid, dcs_buf + 43, 2) != 0) { + if (memcmp(&old_sid[i].sid, dcs_buf + 43, 2)) { if (qso_details) - printf("START from dcs: streamID=%d,%d, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s, source=%.8s\n", - dcs_buf[43],dcs_buf[44], - &dcs_buf[31], - &dcs_buf[39], &dcs_buf[23], - &dcs_buf[7], &dcs_buf[15], - recvlen2,inet_ntoa(fromDst4.sin_addr), source_stn); + printf("START from dcs: streamID=%02x%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s, source=%.8s\n", dcs_buf[44],dcs_buf[43], &dcs_buf[31], &dcs_buf[39], &dcs_buf[23], &dcs_buf[7], &dcs_buf[15], length, fromDst4.GetAddress(), source_stn); // put user into tmp1 memcpy(tmp1, dcs_buf + 31, 8); tmp1[8] = '\0'; // delete the user if exists - for (auto dt_lh_pos = dt_lh_list.begin(); dt_lh_pos != dt_lh_list.end(); dt_lh_pos++) { - if (strcmp((char *)dt_lh_pos->second.c_str(), tmp1) == 0) { + for (auto dt_lh_pos=dt_lh_list.begin(); dt_lh_pos!=dt_lh_list.end(); dt_lh_pos++) { + if (strcmp(dt_lh_pos->second.c_str(), tmp1) == 0) { dt_lh_list.erase(dt_lh_pos); break; } @@ -2993,120 +2550,107 @@ static void runit() sprintf(tmp2, "%ld=r%.6s%c%c", tnow, source_stn, source_stn[7], to_remote_g2[i].from_mod); dt_lh_list[tmp2] = tmp1; - memcpy(old_sid[i].sid, dcs_buf + 43, 2); + memcpy(&old_sid[i].sid, dcs_buf + 43, 2); } to_remote_g2[i].countdown = TIMEOUT; /* new stream ? */ - if ((to_remote_g2[i].in_streamid[0] != dcs_buf[43]) || - (to_remote_g2[i].in_streamid[1] != dcs_buf[44])) { - to_remote_g2[i].in_streamid[0] = dcs_buf[43]; - to_remote_g2[i].in_streamid[1] = dcs_buf[44]; + if (memcmp(&to_remote_g2[i].in_streamid, dcs_buf+43, 2)) { + memcpy(&to_remote_g2[i].in_streamid, dcs_buf+43, 2); dcs_seq[i] = 0xff; /* generate our header */ - - readBuffer2[0] = (unsigned char)(58 & 0xFF); - readBuffer2[1] = (unsigned char)(58 >> 8 & 0x1F); - readBuffer2[1] = (unsigned char)(readBuffer2[1] | 0xFFFFFF80); - memcpy(readBuffer2 + 2, "DSVT", 4); - readBuffer2[6] = 0x10; - readBuffer2[7] = 0x00; - readBuffer2[8] = 0x00; - readBuffer2[9] = 0x00; - readBuffer2[10] = 0x20; - readBuffer2[11] = 0x00; - readBuffer2[12] = 0x01; + SREFDSVT rdsvt; + rdsvt.head[0] = (unsigned char)(58 & 0xFF); + rdsvt.head[1] = (unsigned char)(58 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); + memcpy(rdsvt.dsvt.title, "DSVT", 4); + rdsvt.dsvt.config = 0x10; + rdsvt.dsvt.flaga[0] = rdsvt.dsvt.flaga[1] = rdsvt.dsvt.flaga[2] = 0x00; + rdsvt.dsvt.id = 0x20; + rdsvt.dsvt.flagb[0] = 0x00; + rdsvt.dsvt.flagb[1] = 0x01; if (to_remote_g2[i].from_mod == 'A') - readBuffer2[13] = 0x03; + rdsvt.dsvt.flagb[2] = 0x03; else if (to_remote_g2[i].from_mod == 'B') - readBuffer2[13] = 0x01; + rdsvt.dsvt.flagb[2] = 0x01; else - readBuffer2[13] = 0x02; - readBuffer2[14] = dcs_buf[43]; - readBuffer2[15] = dcs_buf[44]; - readBuffer2[16] = 0x80; - readBuffer2[17] = readBuffer2[18] = readBuffer2[19] = 0x00; - memcpy(readBuffer2 + 20, owner.c_str(), CALL_SIZE); - readBuffer2[27] = to_remote_g2[i].from_mod; - memcpy(readBuffer2 + 28, owner.c_str(), CALL_SIZE); - readBuffer2[35] = 'G'; - memcpy(readBuffer2 + 36, "CQCQCQ ", 8); - memcpy(readBuffer2 + 44, dcs_buf + 31, 8); - memcpy(readBuffer2 + 52, dcs_buf + 39, 4); - calcPFCS(readBuffer2 + 2, 56); + rdsvt.dsvt.flagb[2] = 0x02; + memcpy(&rdsvt.dsvt.streamid, dcs_buf+43, 2); + rdsvt.dsvt.ctrl = 0x80; + rdsvt.dsvt.hdr.flag[0] = rdsvt.dsvt.hdr.flag[1] = rdsvt.dsvt.hdr.flag[2] = 0x00; + memcpy(rdsvt.dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE); + rdsvt.dsvt.hdr.rpt1[7] = to_remote_g2[i].from_mod; + memcpy(rdsvt.dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE); + rdsvt.dsvt.hdr.rpt2[7] = 'G'; + memcpy(rdsvt.dsvt.hdr.urcall, "CQCQCQ ", 8); + memcpy(rdsvt.dsvt.hdr.mycall, dcs_buf + 31, 8); + memcpy(rdsvt.dsvt.hdr.sfx, dcs_buf + 39, 4); + calcPFCS(rdsvt.dsvt.title, 56); /* send the header to the local gateway/repeater */ - for (j = 0; j < 5; j++) - sendto(rptr_sock, readBuffer2+2, 56, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); + for (int j=0; j<5; j++) + Link2Gate.Write(rdsvt.dsvt.title, 56); /* send the data to the donglers */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; - for (j=0; j<5; j++) - sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + SINBOUND *inbound = (SINBOUND *)pos->second; + for (int j=0; j<5; j++) + sendto(ref_g2_sock, rdsvt.head, 58, 0, inbound->addr.GetPointer(), inbound->addr.GetSize()); } } - if ((to_remote_g2[i].in_streamid[0] == dcs_buf[43]) && - (to_remote_g2[i].in_streamid[1] == dcs_buf[44]) && - (dcs_seq[i] != dcs_buf[45])) { + if (0==memcmp(&to_remote_g2[i].in_streamid, dcs_buf+43, 2) && dcs_seq[i]!=dcs_buf[45]) { dcs_seq[i] = dcs_buf[45]; - - readBuffer2[0] = (unsigned char)(29 & 0xFF); - readBuffer2[1] = (unsigned char)(29 >> 8 & 0x1F); - readBuffer2[1] = (unsigned char)(readBuffer2[1] | 0xFFFFFF80); - memcpy(readBuffer2 + 2, "DSVT", 4); - readBuffer2[6] = 0x20; - readBuffer2[7] = 0x00; - readBuffer2[8] = 0x00; - readBuffer2[9] = 0x00; - readBuffer2[10] = 0x20; - readBuffer2[11] = 0x00; - readBuffer2[12] = 0x01; + SREFDSVT rdsvt; + rdsvt.head[0] = (unsigned char)(29 & 0xFF); + rdsvt.head[1] = (unsigned char)(29 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); + memcpy(rdsvt.dsvt.title, "DSVT", 4); + rdsvt.dsvt.config = 0x20; + rdsvt.dsvt.flaga[0] = rdsvt.dsvt.flaga[1] = rdsvt.dsvt.flaga[2] = 0x00; + rdsvt.dsvt.id = 0x20; + rdsvt.dsvt.flagb[0] = 0x00; + rdsvt.dsvt.flagb[1] = 0x01; if (to_remote_g2[i].from_mod == 'A') - readBuffer2[13] = 0x03; + rdsvt.dsvt.flagb[2] = 0x03; else if (to_remote_g2[i].from_mod == 'B') - readBuffer2[13] = 0x01; + rdsvt.dsvt.flagb[2] = 0x01; else - readBuffer2[13] = 0x02; - readBuffer2[14] = dcs_buf[43]; - readBuffer2[15] = dcs_buf[44]; - readBuffer2[16] = dcs_buf[45]; - memcpy(readBuffer2 + 17, dcs_buf + 46, 12); + rdsvt.dsvt.flagb[2] = 0x02; + memcpy(&rdsvt.dsvt.streamid, dcs_buf+43, 2); + rdsvt.dsvt.ctrl = dcs_buf[45]; + memcpy(rdsvt.dsvt.vasd.voice, dcs_buf+46, 12); /* send the data to the local gateway/repeater */ - sendto(rptr_sock, readBuffer2+2, 27, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); + Link2Gate.Write(rdsvt.dsvt.title, 27); /* send the data to the donglers */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; - sendto(ref_g2_sock, readBuffer2, 29, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + SINBOUND *inbound = (SINBOUND *)pos->second; + sendto(ref_g2_sock, rdsvt.head, 29, 0, inbound->addr.GetPointer(), inbound->addr.GetSize()); } if ((dcs_buf[45] & 0x40) != 0) { - memset(old_sid[i].sid, 0x00, 2); + old_sid[i].sid = 0x0; if (qso_details) - printf("END from dcs: streamID=%d,%d, %d bytes from IP=%s\n", - dcs_buf[43],dcs_buf[44], recvlen2,inet_ntoa(fromDst4.sin_addr)); + printf("END from dcs: streamID=%04x, %d bytes from IP=%s\n", ntohs(rdsvt.dsvt.streamid), length, fromDst4.GetAddress()); - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; dcs_seq[i] = 0xff; } } } } - } else if ((dcs_buf[0] == 'E') && (dcs_buf[1] == 'E') && - (dcs_buf[2] == 'E') && (dcs_buf[3] == 'E')) + } else if (dcs_buf[0]=='E' && dcs_buf[1]=='E' && dcs_buf[2]=='E' && dcs_buf[3]=='E') ; - else if (recvlen2 == 35) + else if (length == 35) ; /* is this a keepalive 22 bytes */ - else if (recvlen2 == 22) { - i = -1; + else if (length == 22) { + int i = -1; if (dcs_buf[17] == 'A') i = 0; else if (dcs_buf[17] == 'B') @@ -3116,12 +2660,9 @@ static void runit() /* It is one of our valid repeaters */ // DG1HT from owner 8 to 7 - if ((i >= 0) && (memcmp(dcs_buf + 9, owner.c_str(), CALL_SIZE-1) == 0)) { + if (i>=0 && 0==memcmp(dcs_buf + 9, owner.c_str(), CALL_SIZE-1)) { /* is that the remote system that we asked to connect to? */ - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) && - (memcmp(to_remote_g2[i].to_call, dcs_buf, 7) == 0) && - (to_remote_g2[i].to_mod == dcs_buf[7])) { + if (fromDst4==to_remote_g2[i].addr && to_remote_g2[i].addr.GetPort()==rmt_dcs_port && 0==memcmp(to_remote_g2[i].cs, dcs_buf, 7) && to_remote_g2[i].to_mod==dcs_buf[7]) { if (!to_remote_g2[i].is_connected) { tracing[i].last_time = time(NULL); @@ -3129,21 +2670,17 @@ static void runit() printf("Connected from: %.*s\n", 8, dcs_buf); print_status_file(); - strcpy(linked_remote_system, to_remote_g2[i].to_call); + strcpy(linked_remote_system, to_remote_g2[i].cs); space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", - to_remote_g2[i].from_mod, - linked_remote_system, - to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); } to_remote_g2[i].countdown = TIMEOUT; } } - } else if (recvlen2 == 14) { /* is this a reply to our link/unlink request: 14 bytes */ - i = -1; + } else if (length == 14) { /* is this a reply to our link/unlink request: 14 bytes */ + int i = -1; if (dcs_buf[8] == 'A') i = 0; else if (dcs_buf[8] == 'B') @@ -3154,46 +2691,33 @@ static void runit() /* It is one of our valid repeaters */ if ((i >= 0) && (memcmp(dcs_buf, owner.c_str(), CALL_SIZE) == 0)) { /* It is from a remote that we contacted */ - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) && - (to_remote_g2[i].from_mod == dcs_buf[8])) { - if ((to_remote_g2[i].to_mod == dcs_buf[9]) && - (memcmp(dcs_buf + 10, "ACK", 3) == 0)) { + if ((fromDst4==to_remote_g2[i].addr) && (to_remote_g2[i].addr.GetPort()==rmt_dcs_port) && (to_remote_g2[i].from_mod == dcs_buf[8])) { + if ((to_remote_g2[i].to_mod == dcs_buf[9]) && (memcmp(dcs_buf + 10, "ACK", 3) == 0)) { to_remote_g2[i].countdown = TIMEOUT; if (!to_remote_g2[i].is_connected) { tracing[i].last_time = time(NULL); to_remote_g2[i].is_connected = true; - printf("Connected from: %.*s\n", 8, to_remote_g2[i].to_call); + printf("Connected from: %.*s\n", 8, to_remote_g2[i].cs); print_status_file(); - strcpy(linked_remote_system, to_remote_g2[i].to_call); + strcpy(linked_remote_system, to_remote_g2[i].cs); space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", - to_remote_g2[i].from_mod, - linked_remote_system, - to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); } } else if (memcmp(dcs_buf + 10, "NAK", 3) == 0) { - printf("Link module %c to [%s] %c is unlinked\n", - to_remote_g2[i].from_mod, to_remote_g2[i].to_call, - to_remote_g2[i].to_mod); - - sprintf(notify_msg, "%c_failed_linked.dat_UNLINKED", - to_remote_g2[i].from_mod); - audio_notify(notify_msg); - - to_remote_g2[i].to_call[0] = '\0'; - memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; + printf("Link module %c to [%s] %c is unlinked\n", to_remote_g2[i].from_mod, to_remote_g2[i].cs, to_remote_g2[i].to_mod); + + sprintf(notify_msg[i], "%c_failed_link.dat_UNLINKED", to_remote_g2[i].from_mod); + + to_remote_g2[i].cs[0] = '\0'; + to_remote_g2[i].addr.Clear(); + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; print_status_file(); } @@ -3203,55 +2727,45 @@ static void runit() FD_CLR (dcs_g2_sock,&fdset); } - if (FD_ISSET(rptr_sock, &fdset)) { - fromlen = sizeof(struct sockaddr_in); - recvlen = recvfrom(rptr_sock, (char *)readBuffer, 100, 0, (struct sockaddr *)&fromRptr,&fromlen); + if (keep_running && FD_ISSET(Gate2Link.GetFD(), &fdset)) { + SDSVT dsvt; + int length = Gate2Link.Read(dsvt.title, 56); - if ( ((recvlen == 58) || (recvlen == 29) || (recvlen == 32)) && - (readBuffer[6] == 0x73) && - (readBuffer[7] == 0x12) && - ((memcmp(readBuffer,"DSTR", 4) == 0) || (memcmp(readBuffer,"CCS_", 4) == 0)) && - (readBuffer[10] == 0x20) && - (readBuffer[8] == 0x00) && - ((readBuffer[9] == 0x30) || (readBuffer[9] == 0x13) || (readBuffer[9] == 0x16)) ) { + if ((length==56 || length==27) && 0==memcmp(dsvt.title,"DSVT", 4U) && dsvt.id==0x20U && (dsvt.config==0x10U || dsvt.config==0x20U)) { - if (recvlen == 58) { + if (length == 56) { if (qso_details) - printf("START from local g2: cntr=%02x %02x, streamID=%d,%d, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s\n", - readBuffer[4], readBuffer[5], - readBuffer[14], readBuffer[15], - readBuffer[17], readBuffer[18], readBuffer[19], - readBuffer + 44, readBuffer + 52, readBuffer + 36, - readBuffer + 28, readBuffer + 20, recvlen, inet_ntoa(fromRptr.sin_addr)); - - /* save mycall */ - memcpy(call, readBuffer + 44, 8); + printf("START from local g2: streamID=%04x, flags=%02x:%02x:%02x, my=%.8s/%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes on %s\n", ntohs(dsvt.streamid), dsvt.hdr.flag[0], dsvt.hdr.flag[1], dsvt.hdr.flag[2], dsvt.hdr.mycall, dsvt.hdr.sfx, dsvt.hdr.urcall, dsvt.hdr.rpt1, dsvt.hdr.rpt2, length, gate2link.c_str()); + + // save mycall + memcpy(call, dsvt.hdr.mycall, 8); call[8] = '\0'; - i = -1; - if (readBuffer[35] == 'A') + int i = -1; + if (dsvt.hdr.rpt1[7] == 'A') i = 0; - else if (readBuffer[35] == 'B') + else if (dsvt.hdr.rpt1[7] == 'B') i = 1; - else if (readBuffer[35] == 'C') + else if (dsvt.hdr.rpt1[7] == 'C') i = 2; if (i >= 0) { - memcpy(dtmf_mycall[i], readBuffer + 44, 8); + // save the first char of urcall + your[i] = dsvt.hdr.urcall[0]; // used by rptr_ack + memcpy(dtmf_mycall[i], dsvt.hdr.mycall, 8); dtmf_mycall[i][8] = '\0'; new_group[i] = true; GPS_seen[i] = false; /* Last Heard */ - //put user into tmp1 - memcpy(tmp1, readBuffer + 44, 8); + memcpy(tmp1, dsvt.hdr.mycall, 8); tmp1[8] = '\0'; // delete the user if exists - for (auto dt_lh_pos = dt_lh_list.begin(); dt_lh_pos != dt_lh_list.end(); dt_lh_pos++) { - if (strcmp((char *)dt_lh_pos->second.c_str(), tmp1) == 0) { + for (auto dt_lh_pos=dt_lh_list.begin(); dt_lh_pos!=dt_lh_list.end(); dt_lh_pos++) { + if (strcmp(dt_lh_pos->second.c_str(), tmp1) == 0) { dt_lh_list.erase(dt_lh_pos); break; } @@ -3263,28 +2777,15 @@ static void runit() } /* add user */ time(&tnow); - if (memcmp(readBuffer,"CCS_", 4) == 0) - sprintf(tmp2, "%ld=r%.7s%c", tnow, "-->CCS ", readBuffer[35]); - else - sprintf(tmp2, "%ld=l%.8s", tnow, readBuffer + 28); + sprintf(tmp2, "%ld=l%.8s", tnow, dsvt.hdr.rpt1); dt_lh_list[tmp2] = tmp1; - memcpy(readBuffer, "DSTR", 4); - - tracing[i].streamid[0] = readBuffer[14]; - tracing[i].streamid[1] = readBuffer[15]; + tracing[i].streamid = dsvt.streamid; tracing[i].last_time = time(NULL); } - if ((memcmp(readBuffer + 36, "CQCQCQ", 6) != 0) && (i >= 0)) { - if ((memcmp(readBuffer + 36, owner.c_str(), CALL_SIZE-1) != 0) && - (readBuffer[43] == 'L') && - (memcmp(readBuffer + 20, owner.c_str(), CALL_SIZE-1) == 0) && - (readBuffer[27] == 'G') && - ((readBuffer[17] == 0x00) || - (readBuffer[17] == 0x08) || - (readBuffer[17] == 0x20) || - (readBuffer[17] == 0x28))) { + if (memcmp(dsvt.hdr.urcall, "CQCQCQ", 6) && i>=0) { + if (memcmp(dsvt.hdr.urcall, owner.c_str(), CALL_SIZE-1) && dsvt.hdr.urcall[7] == 'L' && 0==memcmp(dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE-1) && dsvt.hdr.rpt2[7] == 'G' && (dsvt.hdr.flag[0]==0x00 || dsvt.hdr.flag[0]==0x08 || dsvt.hdr.flag[0]==0x20 || dsvt.hdr.flag[0]==0x28)) { if ( // if there is a black list, is he in the blacklist? (link_blacklist.size() && link_blacklist.end()!=link_blacklist.find(call)) || @@ -3293,25 +2794,25 @@ static void runit() ) { printf("link request denied, unauthorized user [%s]\n", call); } else { + char temp_repeater[CALL_SIZE + 1]; memset(temp_repeater, ' ', CALL_SIZE); - memcpy(temp_repeater, readBuffer + 36, CALL_SIZE - 2); + memcpy(temp_repeater, dsvt.hdr.urcall, CALL_SIZE - 2); temp_repeater[CALL_SIZE] = '\0'; - if ((to_remote_g2[i].to_call[0] == '\0') || /* not linked */ - ((to_remote_g2[i].to_call[0] != '\0') && /* waiting for a link reply that may never arrive */ + if ((to_remote_g2[i].cs[0] == '\0') || /* not linked */ + ((to_remote_g2[i].cs[0] != '\0') && /* waiting for a link reply that may never arrive */ !to_remote_g2[i].is_connected)) - g2link(readBuffer[35], temp_repeater, readBuffer[42]); + g2link(dsvt.hdr.rpt1[7], temp_repeater, dsvt.hdr.urcall[6]); else if (to_remote_g2[i].is_connected) { - strcpy(linked_remote_system, to_remote_g2[i].to_call); + strcpy(linked_remote_system, to_remote_g2[i].cs); space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_already_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_already_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); } } - } else if ((readBuffer[43] == 'U') && (readBuffer[36] == ' ')) { + } else if (0==memcmp(dsvt.hdr.urcall, " U", CALL_SIZE)) { if ( // if there is a black list, is he in the blacklist? (link_blacklist.size() && link_blacklist.end()!=link_blacklist.find(call)) || @@ -3320,16 +2821,14 @@ static void runit() ) { printf("unlink request denied, unauthorized user [%s]\n", call); } else { - if (to_remote_g2[i].to_call[0] != '\0') { - if (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port)) { + if (to_remote_g2[i].cs[0] != '\0') { + if (to_remote_g2[i].addr.GetPort() == rmt_ref_port) { /* Check to see if any other local bands are linked to that same IP */ - for (j = 0; j < 3; j++) { + int j; + for (j=0; j<3; j++) { if (j != i) { - if ((to_remote_g2[j].toDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[j].toDst4.sin_port == htons(rmt_ref_port))) { - printf("Info: Local %c is also linked to %s (different module) %c\n", - to_remote_g2[j].from_mod, - to_remote_g2[j].to_call, to_remote_g2[j].to_mod); + if (to_remote_g2[j].addr==to_remote_g2[i].addr && to_remote_g2[j].addr.GetPort()==rmt_ref_port) { + printf("Info: Local %c is also linked to %s (different module) %c\n", to_remote_g2[j].from_mod, to_remote_g2[j].cs, to_remote_g2[j].to_mod); break; } } @@ -3342,393 +2841,296 @@ static void runit() queryCommand[2] = 24; queryCommand[3] = 0; queryCommand[4] = 0; - sendto(ref_g2_sock, queryCommand, 5, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); + sendto(ref_g2_sock, queryCommand, 5, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); } - } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) { + } else if (to_remote_g2[i].addr.GetPort() == rmt_xrf_port) { strcpy(unlink_request, owner.c_str()); unlink_request[8] = to_remote_g2[i].from_mod; unlink_request[9] = ' '; unlink_request[10] = '\0'; - for (j = 0; j < 5; j++) - sendto(xrf_g2_sock, unlink_request, CALL_SIZE+3, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); + for (int j=0; j<5; j++) + sendto(xrf_g2_sock, unlink_request, CALL_SIZE+3, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); } else { strcpy(cmd_2_dcs, owner.c_str()); cmd_2_dcs[8] = to_remote_g2[i].from_mod; cmd_2_dcs[9] = ' '; cmd_2_dcs[10] = '\0'; - memcpy(cmd_2_dcs + 11, to_remote_g2[i].to_call, 8); + memcpy(cmd_2_dcs + 11, to_remote_g2[i].cs, 8); - for (j=0; j<5; j++) - sendto(dcs_g2_sock, cmd_2_dcs, 19, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); + for (int j=0; j<5; j++) + sendto(dcs_g2_sock, cmd_2_dcs, 19, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); } - printf("Unlinked from [%s] mod %c\n", - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - sprintf(notify_msg, "%c_unlinked.dat_UNLINKED", to_remote_g2[i].from_mod); - audio_notify(notify_msg); + printf("Unlinked from [%s] mod %c\n", to_remote_g2[i].cs, to_remote_g2[i].to_mod); + sprintf(notify_msg[i], "%c_unlinked.dat_UNLINKED", to_remote_g2[i].from_mod); /* now zero out this entry */ - to_remote_g2[i].to_call[0] = '\0'; - memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; + to_remote_g2[i].cs[0] = '\0'; + to_remote_g2[i].addr.Clear(); + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; print_status_file(); } else { - sprintf(notify_msg, "%c_already_unlinked.dat_UNLINKED", readBuffer[35]); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_already_unlinked.dat_UNLINKED", dsvt.hdr.rpt1[7]); } } - } else if ((readBuffer[43] == 'I') && (readBuffer[36] == ' ')) { + } + else if (0 == memcmp(dsvt.hdr.urcall, " I", CALL_SIZE)) { if (to_remote_g2[i].is_connected) { - strcpy(linked_remote_system, to_remote_g2[i].to_call); + strcpy(linked_remote_system, to_remote_g2[i].cs); space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", - to_remote_g2[i].from_mod, - linked_remote_system, - to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); } else { - sprintf(notify_msg, "%c_id.dat_%s_NOT_LINKED", readBuffer[35], owner.c_str()); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_id.dat_%s_NOT_LINKED", dsvt.hdr.rpt1[7], owner.c_str()); } - } else if ((readBuffer[43] == 'X') && (readBuffer[36] == ' ') && (admin.find(call) != admin.end())) { // only ADMIN can execute scripts - if (readBuffer[42] != ' ') { - memset(system_cmd, '\0', sizeof(system_cmd)); - snprintf(system_cmd, FILENAME_MAX, "%s/exec_%c.sh %s %c &", - announce_dir.c_str(), - readBuffer[42], call, readBuffer[35]); - printf("Executing %s\n", system_cmd); - system(system_cmd); + } + else if (0==memcmp(dsvt.hdr.urcall, " ", 6) && dsvt.hdr.urcall[7]=='X') { // execute a script + if (dsvt.hdr.urcall[6] != ' ') { // there has to be a char here + bool user_ok = true; + if (admin.size()>0 && admin.end()==admin.find(call)) { // only admins (if defined) can execute scripts + printf("%s not found in the link_admin list!\n", call); + user_ok = false; + } + if (user_ok) { + memset(system_cmd, '\0', sizeof(system_cmd)); + snprintf(system_cmd, FILENAME_MAX, "%s/exec_%c.sh %s %c &", announce_dir.c_str(), dsvt.hdr.urcall[6], call, dsvt.hdr.rpt1[7]); + printf("Executing %s\n", system_cmd); + system(system_cmd); + } } - } else if ((readBuffer[42] == 'D') && (readBuffer[36] == ' ') && (admin.find(call) != admin.end())) { // only ADMIN can block dongle users - if (readBuffer[43] == '1') { + } + else if (0==memcmp(dsvt.hdr.urcall, " ", 6) && dsvt.hdr.urcall[6]=='D' && admin.find(call)!=admin.end()) { // only ADMIN can block dongle users + if (dsvt.hdr.urcall[7] == '1') { max_dongles = saved_max_dongles; printf("Dongle connections are now allowed\n"); - } else if (readBuffer[43] == '0') { + } else if (dsvt.hdr.urcall[7] == '0') { inbound_list.clear(); max_dongles = 0; printf("Dongle connections are now disallowed\n"); } - } else if ((readBuffer[43] == 'F') && (readBuffer[36] == ' ') && (admin.find(call) != admin.end())) { // only ADMIN can reload gwys.txt + } + else if (0==memcmp(dsvt.hdr.urcall, " F", CALL_SIZE) && admin.find(call)!=admin.end()) { // only ADMIN can reload gwys.txt gwy_list.clear(); load_gwys(gwys); } } /* send data to the donglers */ + SREFDSVT rdsvt; if (inbound_list.size() > 0) { - readBuffer2[0] = (unsigned char)(58 & 0xFF); - readBuffer2[1] = (unsigned char)(58 >> 8 & 0x1F); - readBuffer2[1] = (unsigned char)(readBuffer2[1] | 0xFFFFFF80); - - memcpy(readBuffer2 + 2, "DSVT", 4); - readBuffer2[6] = 0x10; - readBuffer2[7] = 0x00; - readBuffer2[8] = 0x00; - readBuffer2[9] = 0x00; - readBuffer2[10] = readBuffer[10]; - readBuffer2[11] = readBuffer[11]; - readBuffer2[12] = readBuffer[12]; - readBuffer2[13] = readBuffer[13]; - memcpy(readBuffer2 + 14, readBuffer + 14, 44); - memcpy(readBuffer2 + 20, owner.c_str(), CALL_SIZE); - readBuffer2[27] = readBuffer[35]; - memcpy(readBuffer2 + 28, owner.c_str(), CALL_SIZE); - readBuffer2[35] = 'G'; - memcpy(&readBuffer2[36], "CQCQCQ ", 8); + memset(rdsvt.head, 0U, 58U); + rdsvt.head[0] = (unsigned char)(58 & 0xFF); + rdsvt.head[1] = (unsigned char)(58 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); + + memcpy(rdsvt.dsvt.title, dsvt.title, 56); + memcpy(rdsvt.dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE); + rdsvt.dsvt.hdr.rpt1[7] = dsvt.hdr.rpt1[7]; + memcpy(rdsvt.dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE); + rdsvt.dsvt.hdr.rpt2[7] = 'G'; + memcpy(rdsvt.dsvt.hdr.urcall, "CQCQCQ ", 8); + calcPFCS(rdsvt.dsvt.title, 56); for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; - for (j=0; j<5; j++) - sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + SINBOUND *inbound = (SINBOUND *)pos->second; + for (int j=0; j<5; j++) + sendto(ref_g2_sock, rdsvt.head, 58, 0, inbound->addr.GetPointer(), inbound->addr.GetSize()); } } if (i >= 0) { /* do we have to broadcast ? */ /* make sure the source is linked to xrf */ - if ((to_remote_g2[i].is_connected) && - (memcmp(to_remote_g2[i].to_call, "XRF", 3) == 0) && - /* only CQCQCQ */ - (memcmp(readBuffer + 20, owner.c_str(), CALL_SIZE-1) == 0) && - (memcmp(readBuffer + 36, "CQCQCQ", 6) == 0) && - (readBuffer[27] == 'G')) { + if (to_remote_g2[i].is_connected && 0==memcmp(to_remote_g2[i].cs, "XRF", 3) && 0==memcmp(dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE-1) && dsvt.hdr.rpt2[7]=='G' && 0==memcmp(dsvt.hdr.urcall, "CQCQCQ", 6)) { brd_from_rptr_idx = 0; - streamid_raw = (readBuffer[14] * 256U) + readBuffer[15]; - - for (j = 0; j < 3; j++) { - if ((j != i) && - (to_remote_g2[j].is_connected) && - (memcmp(to_remote_g2[j].to_call, to_remote_g2[i].to_call, 8) == 0) && - (to_remote_g2[j].to_mod == to_remote_g2[i].to_mod) && - (to_remote_g2[j].to_mod != 'E')) { - memcpy(fromrptr_torptr_brd, "DSVT", 4); - fromrptr_torptr_brd[4] = 0x10; - fromrptr_torptr_brd[5] = 0x00; - fromrptr_torptr_brd[6] = 0x00; - fromrptr_torptr_brd[7] = 0x00; - fromrptr_torptr_brd[8] = readBuffer[10]; - fromrptr_torptr_brd[9] = readBuffer[11]; - fromrptr_torptr_brd[10] = readBuffer[12]; - fromrptr_torptr_brd[11] = readBuffer[13]; - memcpy(fromrptr_torptr_brd + 12, readBuffer + 14, 44); - - streamid_raw ++; - if (streamid_raw == 0) - streamid_raw ++; - fromrptr_torptr_brd[12] = streamid_raw / 256U; - fromrptr_torptr_brd[13] = streamid_raw % 256U; + streamid_raw = ntohs(dsvt.streamid); + + for (int j=0; j<3; j++) { + if (j!=i && to_remote_g2[j].is_connected && 0==memcmp(to_remote_g2[j].cs, to_remote_g2[i].cs, 8) && to_remote_g2[j].to_mod==to_remote_g2[i].to_mod && to_remote_g2[j].to_mod!='E') { + memcpy(fromrptr_torptr_brd.title, dsvt.title, 56); - memcpy(fromrptr_torptr_brd + 18, owner.c_str(), CALL_SIZE); - fromrptr_torptr_brd[25] = to_remote_g2[j].from_mod; - memcpy(fromrptr_torptr_brd + 26, owner.c_str(), CALL_SIZE); - fromrptr_torptr_brd[33] = 'G'; + if (++streamid_raw == 0) + streamid_raw++; + fromrptr_torptr_brd.streamid = htons(streamid_raw); - memcpy(fromrptr_torptr_brd + 34, "CQCQCQ ", 8); + memcpy(fromrptr_torptr_brd.hdr.rpt1, owner.c_str(), CALL_SIZE); + fromrptr_torptr_brd.hdr.rpt1[7] = to_remote_g2[j].from_mod; + memcpy(fromrptr_torptr_brd.hdr.rpt2, owner.c_str(), CALL_SIZE); + fromrptr_torptr_brd.hdr.rpt2[7] = 'G'; - calcPFCS(fromrptr_torptr_brd, 56); + memcpy(fromrptr_torptr_brd.hdr.urcall, "CQCQCQ ", CALL_SIZE); - sendto(xrf_g2_sock, fromrptr_torptr_brd, 56, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + calcPFCS(fromrptr_torptr_brd.title, 56); - brd_from_rptr.from_rptr_streamid[0] = readBuffer[14]; - brd_from_rptr.from_rptr_streamid[1] = readBuffer[15]; - brd_from_rptr.to_rptr_streamid[brd_from_rptr_idx][0] = fromrptr_torptr_brd[12]; - brd_from_rptr.to_rptr_streamid[brd_from_rptr_idx][1] = fromrptr_torptr_brd[13]; + Link2Gate.Write(fromrptr_torptr_brd.title, 56); + + brd_from_rptr.from_rptr_streamid = dsvt.streamid; + brd_from_rptr.to_rptr_streamid[brd_from_rptr_idx] = fromrptr_torptr_brd.streamid; brd_from_rptr_idx ++; } } } if (to_remote_g2[i].is_connected) { - if ((memcmp(readBuffer + 20, owner.c_str(), 7) == 0) && - (memcmp(readBuffer + 36, "CQCQCQ", 6) == 0) && - (readBuffer[27] == 'G')) { - to_remote_g2[i].out_streamid[0] = readBuffer[14]; - to_remote_g2[i].out_streamid[1] = readBuffer[15]; - - if ((to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) || - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { - readBuffer2[0] = (unsigned char)(58 & 0xFF); - readBuffer2[1] = (unsigned char)(58 >> 8 & 0x1F); - readBuffer2[1] = (unsigned char)(readBuffer2[1] | 0xFFFFFF80); - - memcpy(readBuffer2 + 2, "DSVT", 4); - readBuffer2[6] = 0x10; - readBuffer2[7] = 0x00; - readBuffer2[8] = 0x00; - readBuffer2[9] = 0x00; - readBuffer2[10] = readBuffer[10]; - readBuffer2[11] = readBuffer[11]; - readBuffer2[12] = readBuffer[12]; - readBuffer2[13] = readBuffer[13]; - memcpy(readBuffer2 + 14, readBuffer + 14, 44); - memset(readBuffer2 + 20, ' ', CALL_SIZE); - memcpy(readBuffer2 + 20, to_remote_g2[i].to_call, - strlen(to_remote_g2[i].to_call)); - readBuffer2[27] = to_remote_g2[i].to_mod; - memset(readBuffer2 + 28, ' ', CALL_SIZE); - memcpy(readBuffer2 + 28, to_remote_g2[i].to_call, - strlen(to_remote_g2[i].to_call)); - readBuffer2[35] = 'G'; - memcpy(&readBuffer2[36], "CQCQCQ ", 8); - - calcPFCS(readBuffer2 + 2,56); - - if (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) { + if (0==memcmp(dsvt.hdr.rpt2, owner.c_str(), 7) && 0==memcmp(dsvt.hdr.urcall, "CQCQCQ", 6) && dsvt.hdr.rpt2[7] == 'G') { + to_remote_g2[i].out_streamid = dsvt.streamid; + + if (to_remote_g2[i].addr.GetPort()==rmt_xrf_port || to_remote_g2[i].addr.GetPort()==rmt_ref_port) { + SREFDSVT rdsvt; + rdsvt.head[0] = (unsigned char)(58 & 0xFF); + rdsvt.head[1] = (unsigned char)(58 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); + + memcpy(rdsvt.dsvt.title, dsvt.title, 56); + memset(rdsvt.dsvt.hdr.rpt1, ' ', CALL_SIZE); + memcpy(rdsvt.dsvt.hdr.rpt1, to_remote_g2[i].cs, strlen(to_remote_g2[i].cs)); + rdsvt.dsvt.hdr.rpt1[7] = to_remote_g2[i].to_mod; + memset(rdsvt.dsvt.hdr.rpt2, ' ', CALL_SIZE); + memcpy(rdsvt.dsvt.hdr.rpt2, to_remote_g2[i].cs, strlen(to_remote_g2[i].cs)); + rdsvt.dsvt.hdr.rpt2[7] = 'G'; + memcpy(rdsvt.dsvt.hdr.urcall, "CQCQCQ ", CALL_SIZE); + calcPFCS(rdsvt.dsvt.title, 56); + + if (to_remote_g2[i].addr.GetPort() == rmt_xrf_port) { /* inform XRF about the source */ - readBuffer2[13] = to_remote_g2[i].from_mod; - - for (j=0; j<5; j++) - sendto(xrf_g2_sock, readBuffer2+2, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + rdsvt.dsvt.flagb[2] = to_remote_g2[i].from_mod; + calcPFCS(rdsvt.dsvt.title, 56); + for (int j=0; j<5; j++) + sendto(xrf_g2_sock, rdsvt.dsvt.title, 56, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); } else { - for (j=0; j<5; j++) - sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + for (int j=0; j<5; j++) + sendto(ref_g2_sock, rdsvt.head, 58, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); } - } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) { - memcpy(rptr_2_dcs[i].mycall, readBuffer + 44, 8); - memcpy(rptr_2_dcs[i].sfx, readBuffer + 52, 4); + } else if (to_remote_g2[i].addr.GetPort() == rmt_dcs_port) { + memcpy(rptr_2_dcs[i].mycall, dsvt.hdr.mycall, CALL_SIZE); + memcpy(rptr_2_dcs[i].sfx, dsvt.hdr.sfx, 4); rptr_2_dcs[i].dcs_rptr_seq = 0; } } } } - } else { + } + else { // length is 27 if (inbound_list.size() > 0) { - readBuffer2[0] = (unsigned char)(29 & 0xFF); - readBuffer2[1] = (unsigned char)(29 >> 8 & 0x1F); - readBuffer2[1] = (unsigned char)(readBuffer2[1] | 0xFFFFFF80); - - memcpy(readBuffer2 + 2, "DSVT", 4); - readBuffer2[6] = 0x20; - readBuffer2[7] = 0x00; - readBuffer2[8] = 0x00; - readBuffer2[9] = 0x00; - readBuffer2[10] = readBuffer[10]; - readBuffer2[11] = readBuffer[11]; - readBuffer2[12] = readBuffer[12]; - readBuffer2[13] = readBuffer[13]; - memcpy(readBuffer2 + 14, readBuffer + 14, 3); - if (recvlen == 29) - memcpy(readBuffer2 + 17, readBuffer + 17, 12); - else - memcpy(readBuffer2 + 17, readBuffer + 20, 12); + SREFDSVT rdsvt; + rdsvt.head[0] = (unsigned char)(29 & 0xFF); + rdsvt.head[1] = (unsigned char)(29 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); + + memcpy(rdsvt.dsvt.title, dsvt.title, 27); for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; - sendto(ref_g2_sock, readBuffer2, 29, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + SINBOUND *inbound = (SINBOUND *)pos->second; + sendto(ref_g2_sock, rdsvt.head, 29, 0, inbound->addr.GetPointer(), inbound->addr.GetSize()); } } - for (i=0; i<3; i++) { - if ((to_remote_g2[i].is_connected) && - (memcmp(to_remote_g2[i].out_streamid, readBuffer + 14, 2) == 0)) { + for (int i=0; i<3; i++) { + if (to_remote_g2[i].is_connected && to_remote_g2[i].out_streamid==dsvt.streamid) { /* check for broadcast */ - if (memcmp(brd_from_rptr.from_rptr_streamid, readBuffer + 14, 2) == 0) { - memcpy(fromrptr_torptr_brd, "DSVT", 4); - fromrptr_torptr_brd[4] = 0x10; - fromrptr_torptr_brd[5] = 0x00; - fromrptr_torptr_brd[6] = 0x00; - fromrptr_torptr_brd[7] = 0x00; - fromrptr_torptr_brd[8] = readBuffer[10]; - fromrptr_torptr_brd[9] = readBuffer[11]; - fromrptr_torptr_brd[10] = readBuffer[12]; - fromrptr_torptr_brd[11] = readBuffer[13]; - memcpy(fromrptr_torptr_brd + 12, readBuffer + 14, 3); - - if (recvlen == 29) - memcpy(fromrptr_torptr_brd + 15, readBuffer + 17, 12); - else - memcpy(fromrptr_torptr_brd + 15, readBuffer + 20, 12); - - if ((brd_from_rptr.to_rptr_streamid[0][0] != 0x00) || - (brd_from_rptr.to_rptr_streamid[0][1] != 0x00)) { - fromrptr_torptr_brd[12] = brd_from_rptr.to_rptr_streamid[0][0]; - fromrptr_torptr_brd[13] = brd_from_rptr.to_rptr_streamid[0][1]; - sendto(xrf_g2_sock, fromrptr_torptr_brd, 27, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); + if (brd_from_rptr.from_rptr_streamid == dsvt.streamid) { + memcpy(fromrptr_torptr_brd.title, dsvt.title, 27); + if (brd_from_rptr.to_rptr_streamid[0]) { + fromrptr_torptr_brd.streamid = brd_from_rptr.to_rptr_streamid[0]; + Link2Gate.Write(fromrptr_torptr_brd.title, 27); } - if ((brd_from_rptr.to_rptr_streamid[1][0] != 0x00) || - (brd_from_rptr.to_rptr_streamid[1][1] != 0x00)) { - fromrptr_torptr_brd[12] = brd_from_rptr.to_rptr_streamid[1][0]; - fromrptr_torptr_brd[13] = brd_from_rptr.to_rptr_streamid[1][1]; - sendto(xrf_g2_sock, fromrptr_torptr_brd, 27, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); + if (brd_from_rptr.to_rptr_streamid[1]) { + fromrptr_torptr_brd.streamid = brd_from_rptr.to_rptr_streamid[1]; + Link2Gate.Write(fromrptr_torptr_brd.title, 27); } - if ((readBuffer[16] & 0x40) != 0) { - brd_from_rptr.from_rptr_streamid[0] = brd_from_rptr.from_rptr_streamid[1] = 0x00; - brd_from_rptr.to_rptr_streamid[0][0] = brd_from_rptr.to_rptr_streamid[0][1] = 0x00; - brd_from_rptr.to_rptr_streamid[1][0] = brd_from_rptr.to_rptr_streamid[1][1] = 0x00; + if (dsvt.ctrl & 0x40U) { + brd_from_rptr.from_rptr_streamid = brd_from_rptr.to_rptr_streamid[0] = brd_from_rptr.to_rptr_streamid[1] = 0x0; brd_from_rptr_idx = 0; } } - if ((to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) || - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { - readBuffer2[0] = (unsigned char)(29 & 0xFF); - readBuffer2[1] = (unsigned char)(29 >> 8 & 0x1F); - readBuffer2[1] = (unsigned char)(readBuffer2[1] | 0xFFFFFF80); - - memcpy(readBuffer2 + 2, "DSVT", 4); - readBuffer2[6] = 0x20; - readBuffer2[7] = 0x00; - readBuffer2[8] = 0x00; - readBuffer2[9] = 0x00; - readBuffer2[10] = readBuffer[10]; - readBuffer2[11] = readBuffer[11]; - readBuffer2[12] = readBuffer[12]; - readBuffer2[13] = readBuffer[13]; - memcpy(readBuffer2 + 14, readBuffer + 14, 3); - if (recvlen == 29) - memcpy(readBuffer2 + 17, readBuffer + 17, 12); - else - memcpy(readBuffer2 + 17, readBuffer + 20, 12); - - if (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) { + if (to_remote_g2[i].addr.GetPort()==rmt_xrf_port || to_remote_g2[i].addr.GetPort()==rmt_ref_port) { + SREFDSVT rdsvt; + rdsvt.head[0] = (unsigned char)(29 & 0xFF); + rdsvt.head[1] = (unsigned char)(29 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); + + memcpy(rdsvt.dsvt.title, dsvt.title, 27); + + if (to_remote_g2[i].addr.GetPort() == rmt_xrf_port) { /* inform XRF about the source */ - readBuffer2[13] = to_remote_g2[i].from_mod; + rdsvt.dsvt.flagb[2] = to_remote_g2[i].from_mod; - sendto(xrf_g2_sock, readBuffer2+2, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port)) - sendto(ref_g2_sock, readBuffer2, 29, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) { - memset(dcs_buf, 0x00, 600); + sendto(xrf_g2_sock, rdsvt.dsvt.title, 27, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); + } else if (to_remote_g2[i].addr.GetPort() == rmt_ref_port) + sendto(ref_g2_sock, rdsvt.head, 29, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); + } else if (to_remote_g2[i].addr.GetPort() == rmt_dcs_port) { + memset(dcs_buf, 0x0, 600); dcs_buf[0] = dcs_buf[1] = dcs_buf[2] = '0'; dcs_buf[3] = '1'; - dcs_buf[4] = dcs_buf[5] = dcs_buf[6] = 0x00; - memcpy(dcs_buf + 7, to_remote_g2[i].to_call, 8); + dcs_buf[4] = dcs_buf[5] = dcs_buf[6] = 0x0; + memcpy(dcs_buf + 7, to_remote_g2[i].cs, 8); dcs_buf[14] = to_remote_g2[i].to_mod; memcpy(dcs_buf + 15, owner.c_str(), CALL_SIZE); dcs_buf[22] = to_remote_g2[i].from_mod; memcpy(dcs_buf + 23, "CQCQCQ ", 8); memcpy(dcs_buf + 31, rptr_2_dcs[i].mycall, 8); memcpy(dcs_buf + 39, rptr_2_dcs[i].sfx, 4); - dcs_buf[43] = readBuffer[14]; /* streamid0 */ - dcs_buf[44] = readBuffer[15]; /* streamid1 */ - dcs_buf[45] = readBuffer[16]; /* cycle sequence */ - memcpy(dcs_buf + 46, readBuffer + 17, 12); + memcpy(dcs_buf + 43, &dsvt.streamid, 2); + dcs_buf[45] = dsvt.ctrl; /* cycle sequence */ + memcpy(dcs_buf + 46, dsvt.vasd.voice, 12); dcs_buf[58] = (rptr_2_dcs[i].dcs_rptr_seq >> 0) & 0xff; dcs_buf[59] = (rptr_2_dcs[i].dcs_rptr_seq >> 8) & 0xff; dcs_buf[60] = (rptr_2_dcs[i].dcs_rptr_seq >> 16) & 0xff; - rptr_2_dcs[i].dcs_rptr_seq ++; + rptr_2_dcs[i].dcs_rptr_seq++; dcs_buf[61] = 0x01; dcs_buf[62] = 0x00; - sendto(dcs_g2_sock, dcs_buf, 100, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); + sendto(dcs_g2_sock, dcs_buf, 100, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); } - if ((readBuffer[16] & 0x40) != 0) { - to_remote_g2[i].out_streamid[0] = 0x00; - to_remote_g2[i].out_streamid[1] = 0x00; + if (dsvt.ctrl & 0x40U) { + to_remote_g2[i].out_streamid = 0x0; } break; } } - for (i = 0; i < 3; i++) { - if (memcmp(tracing[i].streamid, readBuffer + 14, 2) == 0) { + for (int i=0; i<3; i++) { + if (tracing[i].streamid == dsvt.streamid) { /* update the last time RF user talked */ tracing[i].last_time = time(NULL); - if ((readBuffer[16] & 0x40) != 0) { + if (dsvt.ctrl & 0x40U) { if (qso_details) - printf("END from local g2: cntr=%02x %02x, streamID=%d,%d, %d bytes\n", - readBuffer[4], readBuffer[5], - readBuffer[14],readBuffer[15],recvlen); + printf("END from local g2: streamID=%04x, %d bytes\n", ntohs(dsvt.streamid), length); - if (bool_rptr_ack) - rptr_ack(i); + if ('\0' == notify_msg[i][0]) { + if (bool_rptr_ack && ' ' != your[i]) + rptr_ack(i); + } memset(dtmf_mycall[i], 0, sizeof(dtmf_mycall[i])); new_group[i] = true; GPS_seen[i] = false; - tracing[i].streamid[0] = 0x00; - tracing[i].streamid[1] = 0x00; + tracing[i].streamid = 0x0; } else { if (!GPS_seen[i]) { - if (recvlen == 29) - memcpy(tmp_txt, readBuffer + 26, 3); - else - memcpy(tmp_txt, readBuffer + 29, 3); + memcpy(tmp_txt, dsvt.vasd.text, 3); - if ((tmp_txt[0] != 0x55) || (tmp_txt[1] != 0x2d) || (tmp_txt[2] != 0x16)) { + if (tmp_txt[0]!=0x55 || tmp_txt[1]!=0x2d || tmp_txt[2]!=0x16) { if (new_group[i]) { tmp_txt[0] = tmp_txt[0] ^ 0x70; header_type = tmp_txt[0] & 0xf0; - - if ((header_type == 0x50) || /* header */ - (header_type == 0xc0)) /* squelch */ + // header squelch + if (header_type== 0x50 || header_type==0xc0) new_group[i] = false; else if (header_type == 0x30) { /* GPS or GPS id or APRS */ GPS_seen[i] = true; @@ -3769,259 +3171,252 @@ static void runit() } } } - FD_CLR (rptr_sock,&fdset); + FD_CLR (Gate2Link.GetFD(), &fdset); + } + for (int i=0; i<3; i++) { + if (keep_running && notify_msg[i][0] && 0x0U == tracing[i].streamid) { + PlayAudioNotifyThread(notify_msg[i]); + notify_msg[i][0] = '\0'; + } } } } -void audio_notify(char *msg) +void CQnetLink::PlayAudioNotifyThread(char *msg) { - if (!announce) + if (! announce) return; - short int i = 0; - static char notify_msg[3][64]; + if (msg[0]<'A' || msg[0]>'C') { + fprintf(stderr, "Improper module in msg '%s'\n", msg); + return; + } - if (*msg == 'A') - i = 0; - else if (*msg == 'B') - i = 1; - else if (*msg == 'C') - i = 2; + SECHO edata; + + edata.is_linked = (NULL == strstr(msg, "_linked.dat_LINKED_")) ? false : true; + char *p = strstr(msg, ".dat"); + if (NULL == p) { + fprintf(stderr, "Improper AMBE data file in msg '%s'\n", msg); + return; + } + if ('_' == p[4]) { + std::string message(p+5); + message.resize(20, ' '); + strcpy(edata.message, message.c_str()); + for (int i=0; i<20; i++) { + if ('_' == edata.message[i]) + edata.message[i] = ' '; + } + } else { + strcpy(edata.message, "QnetGateway Message "); + } + p[4] = '\0'; + snprintf(edata.file, FILENAME_MAX, "%s/%s", announce_dir.c_str(), msg+2); + + memcpy(edata.header.title, "DSVT", 4); + edata.header.config = 0x10U; + edata.header.flaga[0] = edata.header.flaga[1] = edata.header.flaga[2] = 0x0U; + edata.header.id = 0x20; + edata.header.streamid = Random.NewStreamID(); + edata.header.ctrl = 0x80U; + edata.header.hdr.flag[0] = edata.header.hdr.flag[1] = edata.header.hdr.flag[2] = 0x0U; + memcpy(edata.header.hdr.rpt1, owner.c_str(), CALL_SIZE); + edata.header.hdr.rpt1[7] = msg[0]; + memcpy(edata.header.hdr.rpt2, owner.c_str(), CALL_SIZE); + edata.header.hdr.rpt2[7] = 'G'; + memcpy(edata.header.hdr.urcall, "CQCQCQ ", CALL_SIZE); + memcpy(edata.header.hdr.mycall, owner.c_str(), CALL_SIZE); + memcpy(edata.header.hdr.sfx, "RPTR", 4); + calcPFCS(edata.header.title, 56); - strcpy(notify_msg[i], msg); try { - std::async(std::launch::async, AudioNotifyThread, notify_msg[i]); + std::async(std::launch::async, &CQnetLink::AudioNotifyThread, this, std::ref(edata)); } catch (const std::exception &e) { printf ("Failed to start AudioNotifyThread(). Exception: %s\n", e.what()); } return; } -static void AudioNotifyThread(char *arg) +void CQnetLink::AudioNotifyThread(SECHO &edata) { - char notify_msg[64]; - - strcpy(notify_msg, (char *)arg); - - unsigned short rlen = 0; - size_t nread = 0; - unsigned char dstar_buf[56]; - bool useTEXT = false; - short int TEXT_idx = 0; - char RADIO_ID[21]; - char temp_file[FILENAME_MAX + 1]; - FILE *fp = NULL; - char mod; - char *p = NULL; - u_int16_t streamid_raw = 0; - unsigned int aseed; - time_t tnow = 0; struct sigaction act; - - /* example: A_linked.dat_LINKED_TO_XRF005_A */ - /* example: A_unlinked.dat */ - /* example: A_failed_linked.dat */ - act.sa_handler = sigCatch; sigemptyset(&act.sa_mask); act.sa_flags = SA_RESTART; if (sigaction(SIGTERM, &act, 0) != 0) { - printf("sigaction-TERM failed, error=%d\n", errno); + fprintf(stderr, "sigaction-TERM failed, error=%d\n", errno); return; } if (sigaction(SIGINT, &act, 0) != 0) { - printf("sigaction-INT failed, error=%d\n", errno); + fprintf(stderr, "sigaction-INT failed, error=%d\n", errno); return; } - memset(RADIO_ID, ' ', 20); - RADIO_ID[20] = '\0'; - - mod = notify_msg[0]; + char mod = edata.header.hdr.rpt1[7]; if ((mod != 'A') && (mod != 'B') && (mod != 'C')) { - printf("Invalid module %c in %s\n", mod, notify_msg); - return; - } - - p = strstr(notify_msg, ".dat"); - if (!p) { - printf("Incorrect filename in %s\n", notify_msg); + fprintf(stderr, "Invalid module %c in %s\n", mod, edata.file); return; } - if (p[4] == '_') { - useTEXT = true; - memcpy(RADIO_ID, p + 5, (strlen(p + 5) > 20)?20:strlen(p + 5)); - for (TEXT_idx = 0; TEXT_idx < 20; TEXT_idx++) { - RADIO_ID[TEXT_idx] = toupper(RADIO_ID[TEXT_idx]); - if (RADIO_ID[TEXT_idx] == '_') - RADIO_ID[TEXT_idx] = ' '; - } - TEXT_idx = 0; - p[4] = '\0'; - } else - useTEXT = false; - sleep(delay_before); - memset(temp_file, '\0', sizeof(temp_file)); - snprintf(temp_file, FILENAME_MAX, "%s/%s", announce_dir.c_str(), notify_msg + 2); - printf("sending File:[%s], mod:[%c], RADIO_ID=[%s]\n", temp_file, mod, RADIO_ID); + printf("sending File:[%s], mod:[%c], RADIO_ID=[%s]\n", edata.file, mod, edata.message); - fp = fopen(temp_file, "rb"); - if (!fp) { - printf("Failed to open file %s for reading\n", temp_file); + struct stat sbuf; + if (stat(edata.file, &sbuf)) { + fprintf(stderr, "can't stat %s\n", edata.file); return; } - /* stupid DVTOOL + 4 byte num_of_records */ - nread = fread(dstar_buf, 10, 1, fp); - if (nread != 1) { - printf("Cant read first 10 bytes from %s\n", temp_file); - fclose(fp); - return; - } - if (memcmp(dstar_buf, "DVTOOL", 6) != 0) { - printf("DVTOOL keyword not found in %s\n", temp_file); - fclose(fp); + if (sbuf.st_size % 9) + printf("Warning %s file size is %ld (not a multiple of 9)!\n", edata.file, sbuf.st_size); + int ambeblocks = (int)sbuf.st_size / 9; + + + FILE *fp = fopen(edata.file, "rb"); + if (!fp) { + fprintf(stderr, "Failed to open file %s for reading\n", edata.file); return; } - time(&tnow); - aseed = tnow + pthread_self(); + Link2Gate.Write(edata.header.title, 56); - while (keep_running) { - /* 2 byte length */ - nread = fread(&rlen, 2, 1, fp); - if (nread != 1) - break; - - if (rlen == 56) - streamid_raw = (::rand_r(&aseed) % 65535U) + 1U; - else if (rlen == 27) - ; - else { - printf("Not 56-byte and not 27-byte in %s\n", temp_file); - break; - } + edata.header.config = 0x20U; - nread = fread(dstar_buf, rlen, 1, fp); + int count; + const unsigned char sdsync[3] = { 0x55U, 0x2DU, 0x16U }; + const unsigned char sdsilence[3] = { 0x16U, 0x29U, 0xF5U }; + for (count=0; count> name >> offset >> size; + if (name.size() && offset.size() && size.size()) { + unsigned long of = std::stoul(offset); + unsigned long sz = std::stoul(size); + speak.push_back(1000U * of + sz); + } + } + indexfile.close(); + } + if (62 == speak.size()) { + printf("read %d indicies from %s\n", (unsigned int)speak.size(), index.c_str()); + } else { + fprintf(stderr, "read unexpected (%d) number of indices from %s\n", (unsigned int)speak.size(), index.c_str()); + speak.clear(); + } + return false; +} +void CQnetLink::Shutdown() +{ + char unlink_request[CALL_SIZE + 3]; + char cmd_2_dcs[19]; /* Clear connections */ queryCommand[0] = 5; @@ -4090,50 +3499,60 @@ int main(int argc, char **argv) queryCommand[2] = 24; queryCommand[3] = 0; queryCommand[4] = 0; - for (i = 0; i < 3; i++) { - if (to_remote_g2[i].to_call[0] != '\0') { - if (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port)) - sendto(ref_g2_sock, queryCommand, 5, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); - else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) { + for (int i=0; i<3; i++) { + if (to_remote_g2[i].cs[0] != '\0') { + if (to_remote_g2[i].addr.GetPort() == rmt_ref_port) + sendto(ref_g2_sock, queryCommand, 5, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); + else if (to_remote_g2[i].addr.GetPort() == rmt_xrf_port) { strcpy(unlink_request, owner.c_str()); unlink_request[8] = to_remote_g2[i].from_mod; unlink_request[9] = ' '; unlink_request[10] = '\0'; - for (j=0; j<5; j++) - sendto(xrf_g2_sock, unlink_request, CALL_SIZE+3, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); + for (int j=0; j<5; j++) + sendto(xrf_g2_sock, unlink_request, CALL_SIZE+3, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); } else { strcpy(cmd_2_dcs, owner.c_str()); cmd_2_dcs[8] = to_remote_g2[i].from_mod; cmd_2_dcs[9] = ' '; cmd_2_dcs[10] = '\0'; - memcpy(cmd_2_dcs + 11, to_remote_g2[i].to_call, 8); + memcpy(cmd_2_dcs + 11, to_remote_g2[i].cs, 8); - for (j=0; j<5; j++) - sendto(dcs_g2_sock, cmd_2_dcs, 19, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); + for (int j=0; j<5; j++) + sendto(dcs_g2_sock, cmd_2_dcs, 19, 0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); } } - to_remote_g2[i].to_call[0] = '\0'; - memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; + to_remote_g2[i].cs[0] = '\0'; + to_remote_g2[i].addr.Clear(); + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; - to_remote_g2[i].out_streamid[0] = 0x00; - to_remote_g2[i].out_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = to_remote_g2[i].out_streamid = 0x0; } /* tell inbound dongles we are down */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; - sendto(ref_g2_sock, queryCommand, 5, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + SINBOUND *inbound = (SINBOUND *)pos->second; + sendto(ref_g2_sock, queryCommand, 5, 0, inbound->addr.GetPointer(), inbound->addr.GetSize()); } inbound_list.clear(); print_status_file(); srv_close(); - printf("g2_link exiting\n"); - return 0; + return; +} + +int main(int argc, char **argv) +{ + if (argc != 2) { + printf("Usage: %s configuration_file\n", argv[0]); + return 1; + } + CQnetLink qnlink; + if (qnlink.Init(argv[1])) + return 1; + printf("QnetLink %s initialized...entering processing loop\n", LINK_VERSION); + qnlink.Process(); + printf("QnetLink exiting\n"); + qnlink.Shutdown(); } diff --git a/QnetLink.h b/QnetLink.h new file mode 100644 index 0000000..308eb48 --- /dev/null +++ b/QnetLink.h @@ -0,0 +1,183 @@ +#pragma once + +/* + * Copyright (C) 2018-2019 by Thomas A. Early N7TAE + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "QnetTypeDefs.h" +#include "SEcho.h" +#include "Random.h" +#include "UnixDgramSocket.h" +#include "SockAddress.h" +#include "Timer.h" + +/*** version number must be x.xx ***/ +#define CALL_SIZE 8 +#define IP_SIZE 15 +#define QUERY_SIZE 56 +#define MAXHOSTNAMELEN 64 +#define TIMEOUT 50 +#define LH_MAX_SIZE 39 + +typedef struct refdsvt_tag { + unsigned char head[2]; + SDSVT dsvt; +} SREFDSVT; + +typedef struct to_remote_g2_tag { + char cs[CALL_SIZE + 1]; + CSockAddress addr; + char from_mod, to_mod; + short countdown; + bool auto_link, is_connected; + unsigned short in_streamid; // incoming from remote systems + unsigned short out_streamid; // outgoing to remote systems +} STOREMOTE; + +// This is the data payload in the map: inbound_list +// This is for inbound dongles +typedef struct inbound_tag { + char call[CALL_SIZE + 1]; // the callsign of the remote + CSockAddress addr; // IP and port of remote + short countdown; // if countdown expires, the connection is terminated + char mod; // A B C This user talked on this module + char client; // dvap, dvdongle +} SINBOUND; + +class CQnetLink { +public: + // functions + CQnetLink(); + ~CQnetLink(); + bool Init(const char *cfgfile); + void Process(); + void Shutdown(); +private: + // functions + void ToUpper(std::string &s); + void UnpackCallsigns(const std::string &str, std::set &set, const std::string &delimiters = ","); + void PrintCallsigns(const std::string &key, const std::set &set); + bool load_gwys(const std::string &filename); + void calcPFCS(unsigned char *packet, int len); + bool read_config(const char *); + bool srv_open(); + void srv_close(); + static void sigCatch(int signum); + void g2link(const char from_mod, const char *call, const char to_mod); + void print_status_file(); + void send_heartbeat(); + bool resolve_rmt(const char *name, const unsigned short port, CSockAddress &addr); + void rptr_ack(short i); + void PlayAudioNotifyThread(char *msg); + void AudioNotifyThread(SECHO &edata); + void RptrAckThread(char *arg); + + /* configuration data */ + std::string login_call, owner, to_g2_external_ip, my_g2_link_ip, gwys, status_file, qnvoice_file, announce_dir; + bool only_admin_login, only_link_unlink, qso_details, log_debug, bool_rptr_ack, announce; + bool dplus_authorize, dplus_reflectors, dplus_repeaters, dplus_priority; + unsigned short rmt_xrf_port, rmt_ref_port, rmt_dcs_port, my_g2_link_port, to_g2_external_port; + int delay_between, delay_before; + std::string link_at_startup[3]; + unsigned int max_dongles, saved_max_dongles; + int rf_inactivity_timer[3]; + const unsigned char REF_ACK[3] = { 3, 96, 0 }; + + // the Key in this inbound_list map is the unique IP address of the remote + std::map inbound_list; + + std::set admin, link_unlink_user, link_blacklist; + + std::map dt_lh_list; + + char notify_msg[3][64]; + + STOREMOTE to_remote_g2[3]; + + // broadcast for data arriving from xrf to local rptr + struct brd_from_xrf_tag { + unsigned short xrf_streamid; // streamid from xrf + unsigned short rptr_streamid[2]; // generated streamid to rptr(s) + } brd_from_xrf; + SDSVT from_xrf_torptr_brd; + short brd_from_xrf_idx; + + // broadcast for data arriving from local rptr to xrf + struct brd_from_rptr_tag { + unsigned short from_rptr_streamid; + unsigned short to_rptr_streamid[2]; + } brd_from_rptr; + SDSVT fromrptr_torptr_brd; + short brd_from_rptr_idx; + + struct tracing_tag { + unsigned short streamid; + time_t last_time; // last time RF user talked + } tracing[3]; + + // input from remote + int xrf_g2_sock, ref_g2_sock, dcs_g2_sock; + CSockAddress fromDst4; + + // unix sockets to gateway + std::string link2gate, gate2link; + CUnixDgramReader Gate2Link; + CUnixDgramWriter Link2Gate; + + // input from our own local repeater + struct sockaddr_in fromRptr; + + fd_set fdset; + struct timeval tv; + + static std::atomic keep_running; + + // Used to validate incoming donglers + regex_t preg; + + // the map of remotes + // key is the callsign, data is the host + std::map gwy_list; + + unsigned char queryCommand[QUERY_SIZE]; + + // START: TEXT crap + char dtmf_mycall[3][CALL_SIZE + 1]; + bool new_group[3]; + int header_type; + bool GPS_seen[3]; + unsigned char tmp_txt[3]; + char *p_tmp2; + // END: TEXT crap + + // this is used for the "dashboard and qso_details" to avoid processing multiple headers + struct old_sid_tag { + unsigned short sid; + } old_sid[3]; + + CRandom Random; + + std::vector speak; +}; diff --git a/QnetModem.cpp b/QnetModem.cpp new file mode 100644 index 0000000..2488c82 --- /dev/null +++ b/QnetModem.cpp @@ -0,0 +1,909 @@ +/* + * Copyright (C) 2019 by Thomas A. Early N7TAE + * + * CQnetModem is inspired by {Modem,MMDVMHost}.cpp in + * Jonathan Naylor's brilliant MMDVMHost that is... + * Copyright (C) 2011-2015,2018 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "QnetModem.h" +#include "QnetConfigure.h" + +#define MODEM_VERSION "QnetModem-0.1.3" +#define MAX_RESPONSES 30 + +std::atomic CQnetModem::keep_running(true); + +const unsigned char FRAME_START = 0xE0U; + +const unsigned char TYPE_VERSION = 0x00U; +const unsigned char TYPE_STATUS = 0x01U; +const unsigned char TYPE_CONFIG = 0x02U; +const unsigned char TYPE_MODE = 0x03U; +const unsigned char TYPE_FREQ = 0x04U; + +const unsigned char TYPE_CWID = 0x0AU; + +const unsigned char TYPE_HEADER = 0x10U; +const unsigned char TYPE_DATA = 0x11U; +const unsigned char TYPE_LOST = 0x12U; +const unsigned char TYPE_EOT = 0x13U; + +const unsigned char TYPE_ACK = 0x70U; +const unsigned char TYPE_NACK = 0x7FU; + +CQnetModem::CQnetModem(int mod) +: assigned_module(mod) +, dstarSpace(0U) +, g2_is_active(false) +{ +} + +CQnetModem::~CQnetModem() +{ +} + +bool CQnetModem::VoicePacketIsSync(const unsigned char *text) +{ + return *text==0x55U && *(text+1)==0x2DU && *(text+2)==0x16U; +} + +bool CQnetModem::GetBufferSize() +{ + std::this_thread::sleep_for(std::chrono::seconds(2)); + + for (int i=0; i<6; i++) { + SMODEM frame; + + frame.start = FRAME_START; + frame.length = 0x3U; + frame.type = TYPE_STATUS; + + if (3 != SendToModem(&frame.start)) + return true; + + for (int count = 0; count < MAX_RESPONSES; count++) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + EModemResponse resp = GetModemData(&frame.start, sizeof(SVERSION)); + if (resp == EModemResponse::status) { + dstarSpace = frame.status.dsrsize; + printf("D-Star buffer will hold %u voice frames\n", dstarSpace); + return false; + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + } + fprintf(stderr, "Unable to read the firmware version after six attempts\n"); + return true; +} + +bool CQnetModem::GetVersion() +{ + std::this_thread::sleep_for(std::chrono::seconds(2)); + + for (int i=0; i<6; i++) { + SVERSION frame; + + frame.start = FRAME_START; + frame.length = 0x3U; + frame.type = TYPE_VERSION; + + if (3 != SendToModem(&frame.start)) + return true; + + for (int count = 0; count < MAX_RESPONSES; count++) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + EModemResponse resp = GetModemData(&frame.start, sizeof(SVERSION)); + if (resp == EModemResponse::version && frame.length > 14U) { + frame.version[frame.length-4U] = '\0'; // just to make sure! + if (0 == memcmp(frame.version, "MMDVM ", 6U)) + hardwareType = EHardwareType::mmdvm; + else if (0 == memcmp(frame.version, "DVMEGA", 6U)) + hardwareType = EHardwareType::dvmega; + else if (0 == memcmp(frame.version, "ZUMspot", 7U)) + hardwareType = EHardwareType::zumspot; + else if (0 == memcmp(frame.version, "MMDVM_HS_Hat", 12U)) + hardwareType = EHardwareType::hs_hat; + else if (0 == memcmp(frame.version, "MMDVM_HS_Dual_Hat", 17U)) + hardwareType = EHardwareType::hs_dual_hat; + else if (0 == memcmp(frame.version, "Nano_hotSPOT", 12U)) + hardwareType = EHardwareType::nano_hs; + else if (0 == memcmp(frame.version, "Nano_DV", 7U)) + hardwareType = EHardwareType::nano_dv; + else if (0 == memcmp(frame.version, "MMDVM_HS-", 9U)) + hardwareType = EHardwareType::mmdvm_hs; + else { + hardwareType = EHardwareType::unknown; + } + + printf("MMDVM protocol version: %u, Modem: %s\n", (unsigned int)frame.protocol, (char *)frame.version); + return false; + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + } + fprintf(stderr, "Unable to read the firmware version after six attempts\n"); + return true; +} + +bool CQnetModem::SetFrequency() +{ + uint32_t pocsagFrequency = 433000000U; + SMODEM frame; + + frame.start = FRAME_START; + frame.type = TYPE_FREQ; + + if (hardwareType == EHardwareType::dvmega) + frame.length = 12U; + else { + frame.frequency.level = 255U; + frame.frequency.ps = __builtin_bswap32(htonl(pocsagFrequency)); + + frame.length = 17U; + } + + frame.frequency.zero = 0x0U; + uint32_t rx_frequency = (uint32_t)((RX_FREQUENCY + RX_OFFSET) * 1000000.0); + frame.frequency.rx = __builtin_bswap32(htonl(rx_frequency)); + uint32_t tx_frequency = (uint32_t)((TX_FREQUENCY + TX_OFFSET) * 1000000.0); + frame.frequency.tx = __builtin_bswap32(htonl(tx_frequency)); + + if ((int)frame.length != SendToModem(&frame.start)) + return true; + + int count = 0; + bool got_ack = false; + while (! got_ack) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + switch (GetModemData(&frame.start, sizeof(SMODEM))) { + case EModemResponse::ack: + got_ack = true; + break; + case EModemResponse::nack: + fprintf(stderr, "SET_FREQ failed, returned NACK reason %u\n", frame.nack.reason); + return true; + default: + if (++count >= MAX_RESPONSES) { + fprintf(stderr, "The MMDVM is not responding to the SET_FREQ command!\n"); + return true; + } + break; + } + } + printf("Modem frequencies set: rx=%u(%u) tx=%u(%u) Hz\n", (uint32_t)(1.0E6 * RX_FREQUENCY), rx_frequency, (uint32_t)(1.0E6 * TX_FREQUENCY), tx_frequency); + return false; +} + +bool CQnetModem::Initialize(const char *cfgfile) +{ + if (ReadConfig(cfgfile)) + return true; + + struct sigaction act; + act.sa_handler = &CQnetModem::SignalCatch; + sigemptyset(&act.sa_mask); + if (sigaction(SIGTERM, &act, 0) != 0) { + printf("sigaction-TERM failed, error=%d\n", errno); + return true; + } + if (sigaction(SIGHUP, &act, 0) != 0) { + printf("sigaction-HUP failed, error=%d\n", errno); + return true; + } + if (sigaction(SIGINT, &act, 0) != 0) { + printf("sigaction-INT failed, error=%d\n", errno); + return true; + } + + Modem2Gate.SetUp(modem2gate.c_str()); + if (Gate2Modem.Open(gate2modem.c_str())) + return true; + + serfd = OpenModem(); + if (serfd < 0) + return true; + + if (GetVersion()) + return true; + + if (SetFrequency()) + return true; + + if (SetConfiguration()) + return true; + + if (GetBufferSize()) + return true; + + return false; +} + +bool CQnetModem::SetConfiguration() +{ + SMODEM frame; + memset(&frame.start, 0, sizeof(SMODEM)); // star with a clean slate + frame.start = FRAME_START; + frame.length = 21U; + frame.type = TYPE_CONFIG; + + if (RX_INVERT) + frame.config.flags |= 0x01U; + if (TX_INVERT) + frame.config.flags |= 0x02U; + if (PTT_INVERT) + frame.config.flags |= 0x04U; + if (! DUPLEX) + frame.config.flags |= 0x80U; + + frame.config.mode = 0x1U; // Only D-Star is enabled! + frame.config.tx_delay = (unsigned char)(TX_DELAY / 10); // In 10ms units + frame.config.init_mode = 0x1U; // yup, just D-Star + frame.config.rx_level = (unsigned char)RX_LEVEL; + frame.config.osc_offset = 128U; // Was OscOffset + frame.config.dstar_tx_level = (unsigned char)TX_LEVEL; + frame.config.tx_dc_offset = 128U; + frame.config.rx_dc_offset = 128U; + + // CUtils::dump(1U, "Written", buffer, 21U); + + if (21 != SendToModem(&frame.start)) + return false; + + int count = 0; + bool got_ack = false; + while (! got_ack) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + switch (GetModemData(&frame.start, sizeof(SMODEM))) { + case EModemResponse::ack: + got_ack = true; + break; + case EModemResponse::nack: + fprintf(stderr, "SET_CONFIG failed, returned NACK reason %u\n", frame.nack.reason); + return true; + default: + if (++count >= MAX_RESPONSES) { + fprintf(stderr, "The MMDVM is not responding to the SET_CONFIG command!\n"); + return true; + } + break; + } + } + printf("Modem configuration set for D-Star only\n"); + return false; +} + +int CQnetModem::OpenModem() +{ + int fd = open(MODEM_DEVICE.c_str(), O_RDWR | O_NOCTTY | O_SYNC, 0); + if (fd < 0) { + printf("Failed to open device [%s], error=%d, message=%s\n", MODEM_DEVICE.c_str(), errno, strerror(errno)); + return -1; + } + + if (isatty(fd) == 0) { + printf("Device %s is not a tty device\n", MODEM_DEVICE.c_str()); + close(fd); + return -1; + } + + static termios t; + if (tcgetattr(fd, &t) < 0) { + printf("tcgetattr failed for %s, error=%d, message-%s\n", MODEM_DEVICE.c_str(), errno, strerror(errno)); + close(fd); + return -1; + } + + t.c_lflag &= ~(ECHO | ECHOE | ICANON | IEXTEN | ISIG); + t.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON | IXOFF | IXANY); + t.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS); + t.c_cflag |= CS8; + t.c_oflag &= ~(OPOST); + t.c_cc[VMIN] = 0; + t.c_cc[VTIME] = 10; + + cfsetospeed(&t, B115200); + cfsetispeed(&t, B115200); + + if (tcsetattr(fd, TCSANOW, &t) < 0) { + printf("tcsetattr failed for %s, error=%dm message=%s\n", MODEM_DEVICE.c_str(), errno, strerror(errno)); + close(fd); + return -1; + } + + return fd; +} + +EModemResponse CQnetModem::GetModemData(unsigned char *buf, unsigned int size) +{ + if (size < 4U) { + fprintf(stderr, "Buffer size, %u is too small\n", size); + return EModemResponse::error; + } + + // Get the start byte + int ret = read(serfd, buf, 1U); + if (ret < 0) { + fprintf(stderr, "Error when reading frame start byte: %s\n", strerror(errno)); + return EModemResponse::error; + } else if (ret == 0) { + printf("READ START RETURNED A ZERO!\n"); + return EModemResponse::timeout; + } else if (buf[0] != FRAME_START) + return EModemResponse::timeout; + + //get the length byte + ret = read(serfd, buf+1, 1U); + if (ret < 0) { + fprintf(stderr, "Error when reading frame length: %s\n", strerror(errno)); + return EModemResponse::error; + } else if (ret == 0) { + printf("READ LENGTH RETURNED A ZERO!\n"); + return(EModemResponse::timeout); + } + // is the packet size bigger than a D-Star header (44 bytes)? + unsigned int junk_count = ((unsigned int)buf[1] > size) ? (unsigned int)buf[1] - size : 0; + + // get the type byte + ret = read(serfd, buf+2, 1U); + if (ret < 0) { + fprintf(stderr, "Error when reading frame type: %s\n", strerror(errno)); + return EModemResponse::error; + } else if (ret == 0) { + printf("READ TYPE RETURNED A ZERO!\n"); + return(EModemResponse::timeout); + } + // get the data + unsigned int length = buf[1]; + unsigned int offset = 3; + while (offset < length) { + ret = read(serfd, buf + offset, length - offset); + if (ret < 0) { + printf("Error when reading data: %s\n", strerror(errno)); + return EModemResponse::error; + } + if (ret == 0) { + printf("READ DATA RETURNED A ZERO!\n"); + return(EModemResponse::timeout); + } else + offset += ret; + } + + while (junk_count) { + unsigned char junk[8]; + ret = read(serfd, junk, (junk_count > 8U) ? 8U : junk_count); + if (ret < 0) { + printf("Error when reading junk: %s\n", strerror(errno)); + return EModemResponse::error; + } else if (ret == 0) { + printf("READ junk RETURNED A ZERO!\n"); + return(EModemResponse::timeout); + } else { + junk_count -= (unsigned int)ret; + } + } + + switch (buf[2]) { + case TYPE_ACK: + return EModemResponse::ack; + case TYPE_NACK: + return EModemResponse::nack; + case TYPE_HEADER: + return EModemResponse::header; + case TYPE_DATA: + return EModemResponse::data; + case TYPE_LOST: + return EModemResponse::lost; + case TYPE_EOT: + return EModemResponse::eot; + case TYPE_VERSION: + return EModemResponse::version; + case TYPE_STATUS: + return EModemResponse::status; + default: + return EModemResponse::error; + }; +} + +void CQnetModem::Run(const char *cfgfile) +{ + if (Initialize(cfgfile)) + return; + + int ug2m = Gate2Modem.GetFD(); + printf("gate2modem=%d, serial=%d\n", ug2m, serfd); + + keep_running = true; + + CTimer statusTimer; + CTimer deadTimer; + + while (keep_running) { + + SMODEM frame; + frame.start = FRAME_START; + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(serfd, &readfds); + FD_SET(ug2m, &readfds); + int maxfs = (serfd > ug2m) ? serfd : ug2m; + + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 3000; // select will return a zero after 3 msec of inactivity + + // don't care about writefds and exceptfds: + int ret = select(maxfs+1, &readfds, NULL, NULL, &tv); + if (ret < 0) { + printf("ERROR: Run: select returned err=%d, %s\n", errno, strerror(errno)); + break; + } + + // check for a dead or disconnected radio + if (10.0 < deadTimer.time()) { + printf("no activity from radio for 10 sec. Exiting...\n"); + keep_running = false; + } + + if (keep_running && FD_ISSET(serfd, &readfds)) { + deadTimer.start(); + switch (GetModemData(&frame.start, sizeof(SMODEM))) { + case EModemResponse::data: + case EModemResponse::header: + case EModemResponse::eot: + case EModemResponse::lost: + if (ProcessModem(frame)) + keep_running = false; + break; + case EModemResponse::status: + if (frame.status.flags & 0x02U) + fprintf(stderr, "Modem ADC levels have overflowed\n"); + if (frame.status.flags & 0x04U) + fprintf(stderr, "Modem RX buffer has overflowed\n"); + if (frame.status.flags & 0x08U) + fprintf(stderr, "Modem TX buffer has overflowed\n"); + if (frame.status.flags & 0x20U) + fprintf(stderr, "Modem DAC levels have overflowed\n"); + dstarSpace = frame.status.dsrsize; + break; + default: + break; + } + FD_CLR(serfd, &readfds); + } + + if (keep_running && FD_ISSET(ug2m, &readfds)) { + SDSVT dsvt; + ssize_t len = Gate2Modem.Read(dsvt.title, sizeof(SDSVT)); + + if (len <= 0) { + break; + } + + if (0 == memcmp(dsvt.title, "DSVT", 4) && dsvt.id==0x20U && (dsvt.config==0x10U || dsvt.config==0x20U) && (len==56 || len==27)) { + ProcessGateway(dsvt); + } else { + fprintf(stderr, "Unexpected data, returned %d bytes from the gateway: %02x", int(len), *dsvt.title); + for (ssize_t i=1; i packet_wait) { + // // g2 has timed out + // frame.length = 3U; + // frame.type = TYPE_LOST; + // queue.push(CFrame(&frame.start)); + // g2_is_active = false; + //} + if (! queue.empty()) { + // send queued D-Star frames to modem + CFrame cframe = queue.front(); + const unsigned char type = cframe.type(); + if ((type==TYPE_HEADER && dstarSpace>3U) || ((type==TYPE_DATA || type==TYPE_EOT || type==TYPE_LOST) && dstarSpace>0U)) { + SendToModem(cframe.data()); + queue.pop(); + dstarSpace -= (type==TYPE_HEADER) ? 4U : 1U; + } + } + if (statusTimer.time() > 0.25) { + // request a status update every 250 milliseconds + frame.length = 3U; + frame.type = TYPE_STATUS; + if (3 != SendToModem(&frame.start)) + keep_running = false; + statusTimer.start(); + } + } + } + close(serfd); + Gate2Modem.Close(); +} + +int CQnetModem::SendToModem(const unsigned char *buf) +{ + ssize_t n; + size_t sent = 0; + ssize_t length = buf[1]; + + while ((ssize_t)sent < length) { + n = write(serfd, buf + sent, length - sent); + if (n < 0) { + if (EAGAIN != errno) { + printf("Error %d writing to dvap, message=%s\n", errno, strerror(errno)); + return -1; + } + } + sent += n; + } + + return length; +} + +void CQnetModem::ProcessGateway(const SDSVT &dsvt) +{ + static std::string superframe; + SMODEM frame; // destination + frame.start = FRAME_START; + if (0x10U == dsvt.config) { // write a Header packet + superframe.clear(); + frame.length = 44U; + frame.type = TYPE_HEADER; + memcpy(frame.header.flag, dsvt.hdr.flag, 3); + memcpy(frame.header.r1, dsvt.hdr.rpt2, 8); + memcpy(frame.header.r2, dsvt.hdr.rpt1, 8); + memcpy(frame.header.ur, dsvt.hdr.urcall, 8); + memcpy(frame.header.my, dsvt.hdr.mycall, 8); + memcpy(frame.header.nm, dsvt.hdr.sfx, 4); + memcpy(frame.header.pfcs, dsvt.hdr.pfcs, 2); + queue.push(CFrame(&frame.start)); + PacketWait.start(); + g2_is_active = true; + if (LOG_QSO) + printf("Queued to %s flags=%02x:%02x:%02x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", MODEM_DEVICE.c_str(), frame.header.flag[0], frame.header.flag[1], frame.header.flag[2], frame.header.ur, frame.header.r2, frame.header.r1, frame.header.my, frame.header.nm); + } else { // write a voice data packet + if (g2_is_active) { + //const unsigned char sdsync[3] = { 0x55U, 0x2DU, 0x16U }; + if (dsvt.ctrl & 0x40U) { + if (LOG_DEBUG && superframe.size()) + printf("Final order: %s\n", superframe.c_str()); + frame.length = 3U; + frame.type = TYPE_EOT; + g2_is_active = false; + if (LOG_QSO) + printf("Queued modem end of transmission\n"); + } else { + frame.length = 15U; + frame.type = TYPE_DATA; + memcpy(frame.voice.ambe, dsvt.vasd.voice, 12); + if (LOG_DEBUG) { + const unsigned int ctrl = dsvt.ctrl & 0x3FU; + if (VoicePacketIsSync(dsvt.vasd.text)) { + if (superframe.size() > 65) { + printf("Frame order: %s\n", superframe.c_str()); + superframe.clear(); + } + const char *ch = "#abcdefghijklmnopqrstuvwxyz"; + superframe.append(1, (ctrl<27U) ? ch[ctrl] : '%'); + } else { + const char *ch = "!ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + superframe.append(1, (ctrl<27U) ? ch[ctrl] : '*'); + } + } + } + queue.push(CFrame(&frame.start)); + PacketWait.start(); + } + } +} + +bool CQnetModem::ProcessModem(const SMODEM &frame) +{ + static bool in_stream = false; + static bool first_voice_packet = false; + static short stream_id = 0U; + static unsigned char nextctrl = 21U; + + // create a stream id if this is a header + if (frame.type == TYPE_HEADER) + stream_id = random.NewStreamID(); + + SDSVT dsvt; // destination + // sets most of the params + memcpy(dsvt.title, "DSVT", 4); + memset(dsvt.flaga, 0U, 3U); + dsvt.id = 0x20U; + dsvt.flagb[0] = 0x0U; + dsvt.flagb[1] = 0x1U; + dsvt.flagb[2] = ('B'==RPTR_MOD) ? 0x1U : (('C'==RPTR_MOD) ? 0x2U : 0x3U); + dsvt.streamid = htons(stream_id); + + if (frame.type == TYPE_HEADER) { // header + nextctrl = 21U; + in_stream = first_voice_packet = true; + dsvt.config = 0x10U; + dsvt.ctrl = 0x80U; + + memcpy(dsvt.hdr.flag, frame.header.flag, 3); + dsvt.hdr.flag[0] &= ~0x40U; // clear this bit + memcpy(dsvt.hdr.rpt1, frame.header.r1, 8); + memcpy(dsvt.hdr.rpt2, frame.header.r2, 8); + memcpy(dsvt.hdr.urcall, frame.header.ur, 8); + + memcpy(dsvt.hdr.mycall, frame.header.my, 8); + memcpy(dsvt.hdr.sfx, frame.header.nm, 4); + memcpy(dsvt.hdr.pfcs, frame.header.pfcs, 2); + if (56 != Modem2Gate.Write(dsvt.title, 56)) { + printf("ERROR: ProcessModem: Could not write gateway header packet\n"); + return true; + } + if (LOG_QSO) + printf("Sent DSVT to gateway, streamid=%04x flags=%02x:%02x:%02x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", ntohs(dsvt.streamid), dsvt.hdr.flag[0], dsvt.hdr.flag[1], dsvt.hdr.flag[2], dsvt.hdr.urcall, dsvt.hdr.rpt1, dsvt.hdr.rpt2, dsvt.hdr.mycall, dsvt.hdr.sfx); + } else if (in_stream && (frame.type==TYPE_DATA || frame.type==TYPE_EOT || frame.type==TYPE_LOST)) { // ambe + const unsigned char sync[12] = { 0x9EU,0x8DU,0x32U,0x88U,0x26U,0x1AU,0x3FU,0x61U,0xE8U,0x55U,0x2DU,0x16U }; + const unsigned char silence[12] = { 0x9EU,0x8DU,0x32U,0x88U,0x26U,0x1AU,0x3FU,0x61U,0xE8U,0x70U,0x4FU,0x93U }; + dsvt.config = 0x20U; + if (frame.type == TYPE_DATA) { + + if (first_voice_packet) { // make sure the first voice packet is a sync frame + if (! VoicePacketIsSync(frame.voice.text)) { // create a quite sync voice packet + if (LOG_DEBUG) + printf("Warning: Inserting missing frame sync after header\n"); + dsvt.ctrl = 0U; + memcpy(dsvt.vasd.voice, sync, 12U); + Modem2Gate.Write(dsvt.title, 27); + nextctrl = 0x1U; + } + first_voice_packet = false; + } + + if (VoicePacketIsSync(frame.voice.text)) { + if (nextctrl < 21U) + fprintf(stderr, "Warning: The last superframe had %u frames, inserting missing frame(s)\n", nextctrl); + memcpy(dsvt.vasd.voice, silence, 12U); + while (nextctrl < 21U) { + dsvt.ctrl = nextctrl++; + Modem2Gate.Write(dsvt.title, 27); + } + nextctrl = 0x0U; + } + + if (nextctrl > 20U) { + fprintf(stderr, "Warning: nextctrl=%u, inserting missing sync frame\n", nextctrl); + dsvt.ctrl = 0U; + memcpy(dsvt.vasd.voice, sync, 12U); + Modem2Gate.Write(dsvt.title, 27); + nextctrl = 0x1U; + } + + memcpy(dsvt.vasd.voice, frame.voice.ambe, 12); + } else { + if (frame.type == TYPE_LOST) + printf("Got a TYPE_LOST packet.\n"); + if (0U == nextctrl) { + memcpy(dsvt.vasd.voice, sync, 12); + } else { + memcpy(dsvt.vasd.voice, silence, 12); + } + nextctrl |= 0x40U; + if (LOG_QSO) { + if (frame.type == TYPE_EOT) + printf("Sent DSVT end of streamid=%04x\n", ntohs(dsvt.streamid)); + else + printf("Sent LOST end of streamid=%04x\n", ntohs(dsvt.streamid)); + } + in_stream = false; + } + dsvt.ctrl = nextctrl++; + if (27 != Modem2Gate.Write(dsvt.title, 27)) { + printf("ERROR: ProcessModem: Could not write gateway voice packet\n"); + return true; + } + + } else { + if (in_stream) { + fprintf(stderr, "Warning! Unexpected frame: %02x", frame.start); + for (unsigned int i=1U; i assigned_module) { + // we need to find the lone mmdvmmodem module + for (int i=0; i<3; i++) { + std::string test(modem_path); + test.append(1, 'a'+i); + if (cfg.KeyExists(test)) { + cfg.GetValue(test, estr, type, 1, 16); + if (type.compare("mmdvmmodem")) + continue; // this ain't it! + modem_path.assign(test); + assigned_module = i; + break; + } + } + if (0 > assigned_module) { + fprintf(stderr, "Error: no 'mmdvmmodem' module found\n!"); + return true; + } + } else { + // make sure mmdvmmodem module is defined + modem_path.append(1, 'a' + assigned_module); + if (cfg.KeyExists(modem_path)) { + cfg.GetValue(modem_path, estr, type, 1, 16); + if (type.compare("mmdvmmodem")) { + fprintf(stderr, "%s = %s is not 'mmdvmmodem' type!\n", modem_path.c_str(), type.c_str()); + return true; + } + } else { + fprintf(stderr, "Module '%c' is not defined.\n", 'a'+assigned_module); + return true; + } + } + RPTR_MOD = 'A' + assigned_module; + + cfg.GetValue(modem_path+"_device", type, MODEM_DEVICE, 7, FILENAME_MAX); + cfg.GetValue("gateway_gate2modem"+std::string(1, 'a'+assigned_module), estr, gate2modem, 1, FILENAME_MAX); + cfg.GetValue("gateway_modem2gate", estr, modem2gate, 1, FILENAME_MAX); + + if (cfg.GetValue(modem_path+"_tx_frequency", type, TX_FREQUENCY, 1.0, 6000.0)) + return true; // we have to have a valid frequency + cfg.GetValue(modem_path+"_rx_frequency", type, RX_FREQUENCY, 0.0, 6000.0); + if (RX_FREQUENCY <= 0.0) + RX_FREQUENCY = TX_FREQUENCY; + cfg.GetValue(modem_path+"_tx_offset", type, TX_OFFSET, -10.0, 10.0); + cfg.GetValue(modem_path+"_rx_offset", type, RX_OFFSET, -10.0, 10.0); + cfg.GetValue(modem_path+"_duplex", type, DUPLEX); + cfg.GetValue(modem_path+"_rx_invert", type, RX_INVERT); + cfg.GetValue(modem_path+"_tx_invert", type, TX_INVERT); + cfg.GetValue(modem_path+"_ptt_invert", type, PTT_INVERT); + cfg.GetValue(modem_path+"_tx_delay", type, TX_DELAY, 0, 1000); + cfg.GetValue(modem_path+"_rx_level", type, RX_LEVEL, 0, 255); + cfg.GetValue(modem_path+"_tx_level", type, TX_LEVEL, 0, 255); + cfg.GetValue(modem_path+"_packet_wait", type, PACKET_WAIT, 18, 30); + packet_wait = 1.0E-3 * double(PACKET_WAIT); + + modem_path.append("_callsign"); + if (cfg.KeyExists(modem_path)) { + if (cfg.GetValue(modem_path, type, RPTR, 3, 6)) + return true; + } else { + modem_path.assign("ircddb_login"); + if (cfg.KeyExists(modem_path)) { + if (cfg.GetValue(modem_path, estr, RPTR, 3, 6)) + return true; + } + } + int l = RPTR.length(); + if (l<3 || l>6) { + printf("Call '%s' is invalid length!\n", RPTR.c_str()); + return true; + } else { + for (int i=0; i +#include +#include +#include + +#include +#include "Random.h" // for streamid generation +#include "UnixDgramSocket.h" +#include "QnetTypeDefs.h" +#include "Timer.h" + +#define CALL_SIZE 8 +#define IP_SIZE 15 + +enum class EModemResponse { + ack, + nack, + timeout, + error, + header, + data, + lost, + eot, + status, + version +}; + +enum class EHardwareType { + mmdvm, + dvmega, + zumspot, + hs_hat, + hs_dual_hat, + nano_hs, + nano_dv, + mmdvm_hs, + unknown +}; + +// Icom Terminal and Access Point Mode data structure +#pragma pack(push, 1) +typedef struct version_tag { + unsigned char start; + unsigned char length; + unsigned char type; + unsigned char protocol; + unsigned char version[251]; +} SVERSION; + +typedef struct mmodem_tag { + unsigned char start; // always 0xEOU + unsigned char length; // 3 - 255 + unsigned char type; + // 0x70U acknowledge from modem, ACK + // 0x7FU error from modem, NACK + // 0x00U version + // 0x01U status + // 0x02U configure + // 0x03U mode + // 0x04U frequency + // 0x10U header + // 0x11U data + // 0x12U transmission lost + // 0x13U transmission end + union { + unsigned char ack; // the type being acknowledged + unsigned char mode; // 0 idle, 1 dstar, 2 dmr, 3 ysf, 99 calibration + struct { + unsigned char ack; // the type being acknowledged + unsigned char reason; // reason for the NAK + // 1 - invalid command + // 2 - wrong mode + // 3 - command too long + // 4 - data incorrect + // 5 - Not enough buffer space + } nack; + // don't want to inflate the struct size, so it's here for reference only + //struct { + // unsigned char protocol_version; + // unsigned char version[250]; + //} version; + struct { + unsigned char modes; // 0x1U dstar | 0x2 dmr | 0x4 system fusion + unsigned char status; // 0 idle, 1 dstar, 2 dmr, 3 system fusion, 99 calibration + unsigned char flags; // 0x1 Tx on, 0x2 adc overflow + unsigned char dsrsize; // dstar buffersize + unsigned char dm1size; // drm timeslot 1 buffersize + unsigned char dm2size; // dmr timeslot 2 buffersize + unsigned char ysfsize; // ysf buffersize + } status; + struct { + unsigned char flags; // 0x1 rx 0x2 tx 0x4 ptt 0x8 ysf lodev 0x10 debug 0x80 not duplex + unsigned char mode; // 0x1 dstar 0x2 drm 0x4 ysf 0x8 p25 0x10 nxdx 0x20 pocsag + unsigned char tx_delay; // tx delay in 10 millisecond increments + unsigned char init_mode; // inital state 0 idle 1 dstar 2 dmr 3 ysf 99 calibration + unsigned char rx_level; // rx input level 0-255 + unsigned char cw_tx_level; // cw tx output + unsigned char color; // dmr color 0-15 + unsigned char drm_delay; + unsigned char osc_offset; // 128U + unsigned char dstar_tx_level; + unsigned char dmr_tx_level; + unsigned char ysf_tx_level; + unsigned char p25_tx_level; + unsigned char tx_dc_offset; + unsigned char rx_dc_offset; + unsigned char nxdn_tx_level; + unsigned char ysf_tx_hang; + unsigned char pocsag_tx; + } config; + struct { + unsigned char zero; // should be zero; + uint32_t rx; // receive frequency + uint32_t tx; // transmitter frequency + unsigned char level; // rf level for pocsag? + uint32_t ps; // pocsag frequency, default 433000000U + } frequency; + struct { + unsigned char flag[3]; + unsigned char r2[8]; + unsigned char r1[8]; + unsigned char ur[8]; + unsigned char my[8]; + unsigned char nm[4]; + unsigned char pfcs[2]; + } header; + struct { + unsigned char ambe[9]; + unsigned char text[3]; + } voice; + }; +} SMODEM; +#pragma pack(pop) + +class CFrame +{ +public: + CFrame(const unsigned char *buf) { + memcpy(&frame.start, buf, buf[1]); + } + + CFrame(const CFrame &from) { + memcpy(&frame.start, from.data(), from.size()); + } + + CFrame &operator=(const CFrame &from) { + memcpy(&frame.start, from.data(), from.size()); + return *this; + } + + ~CFrame() {} + + size_t size() const { return (size_t)frame.length; } + + const unsigned char *data() const { return &frame.start; } + unsigned char type() { return frame.type; } + +private: + SMODEM frame; +}; + +class CQnetModem +{ +public: + // functions + CQnetModem(int mod); + ~CQnetModem(); + void Run(const char *cfgfile); + + // data + static std::atomic keep_running; + +private: + int assigned_module; + unsigned int dstarSpace; + bool g2_is_active; + + // functions + bool VoicePacketIsSync(const unsigned char *); + bool Initialize(const char *cfgfile); + static void SignalCatch(const int signum); + void ProcessGateway(const SDSVT &dsvt); + bool ProcessModem(const SMODEM &frame); + int OpenModem(); + int SendToModem(const unsigned char *buf); + EModemResponse GetModemData(unsigned char *buf, unsigned int size); + bool GetVersion(); + bool GetBufferSize(); + bool SetFrequency(); + bool SetConfiguration(); + + // read configuration file + bool ReadConfig(const char *); + + // config data + char RPTR_MOD; + std::string MODEM_DEVICE, RPTR; + double TX_FREQUENCY, RX_FREQUENCY, TX_OFFSET, RX_OFFSET, packet_wait; + int TX_DELAY, RX_LEVEL, TX_LEVEL, PACKET_WAIT; + bool DUPLEX, RX_INVERT, TX_INVERT, PTT_INVERT, LOG_QSO, LOG_DEBUG; + + // parameters + EHardwareType hardwareType; + int serfd; + + + // helpers + CRandom random; + CTimer PacketWait; + + // unix sockets + std::string modem2gate, gate2modem; + CUnixDgramWriter Modem2Gate; + CUnixDgramReader Gate2Modem; + + // Queue + std::queue queue; +}; diff --git a/QnetRelay.cpp b/QnetRelay.cpp index bf5656b..19b17f1 100644 --- a/QnetRelay.cpp +++ b/QnetRelay.cpp @@ -32,13 +32,16 @@ #include #include -#include "versions.h" #include "QnetRelay.h" #include "QnetTypeDefs.h" +#include "QnetConfigure.h" + +#define RELAY_VERSION "QnetRelay-1.1.0" std::atomic CQnetRelay::keep_running(true); -CQnetRelay::CQnetRelay() : +CQnetRelay::CQnetRelay(int mod) : +assigned_module(mod), seed(time(NULL)), COUNTER(0) { @@ -116,22 +119,22 @@ int CQnetRelay::OpenSocket(const std::string &address, unsigned short port) return fd; } -void CQnetRelay::Run(const char *cfgfile) +bool CQnetRelay::Run(const char *cfgfile) { if (Initialize(cfgfile)) - return; + return true; msock = OpenSocket(MMDVM_IP, MMDVM_OUT_PORT); if (msock < 0) - return; + return true; - gsock = OpenSocket(G2_INTERNAL_IP, G2_OUT_PORT); - if (gsock < 0) { - ::close(msock); - return; - } + Modem2Gate.SetUp(modem2gate.c_str()); + if (Gate2Modem.Open(gate2modem.c_str())) + return true; - printf("msock=%d, gsock=%d\n", msock, gsock); + int fd = Gate2Modem.GetFD(); + + printf("msock=%d, gateway=%d\n", msock, fd); keep_running = true; @@ -139,8 +142,8 @@ void CQnetRelay::Run(const char *cfgfile) fd_set readfds; FD_ZERO(&readfds); FD_SET(msock, &readfds); - FD_SET(gsock, &readfds); - int maxfs = (msock > gsock) ? msock : gsock; + FD_SET(fd, &readfds); + int maxfs = (msock > fd) ? msock : fd; // don't care about writefds and exceptfds: // and we'll wait as long as needed @@ -163,30 +166,26 @@ void CQnetRelay::Run(const char *cfgfile) len = ::recvfrom(msock, buf, 100, 0, (sockaddr *)&addr, &size); if (len < 0) { - printf("ERROR: Run: recvfrom(mmdvm) return error %d, %s\n", errno, strerror(errno)); + fprintf(stderr, "ERROR: Run: recvfrom(mmdvmhost) return error %d: %s\n", errno, strerror(errno)); break; } if (ntohs(addr.sin_port) != MMDVM_IN_PORT) - printf("DEBUG: Run: read from msock but port was %u, expected %u.\n", ntohs(addr.sin_port), MMDVM_IN_PORT); + fprintf(stderr, "DEBUG: Run: read from msock but port was %u, expected %u.\n", ntohs(addr.sin_port), MMDVM_IN_PORT); } - if (FD_ISSET(gsock, &readfds)) { - len = ::recvfrom(gsock, buf, 100, 0, (sockaddr *)&addr, &size); + if (FD_ISSET(fd, &readfds)) { + len = Gate2Modem.Read(buf, 100); if (len < 0) { - printf("ERROR: Run: recvfrom(gsock) returned error %d, %s\n", errno, strerror(errno)); + fprintf(stderr, "ERROR: Run: Gate2Modem.Read() returned error %d: %s\n", errno, strerror(errno)); break; } - - if (ntohs(addr.sin_port) != G2_IN_PORT) - printf("DEBUG: Run: read from gsock but the port was %u, expected %u\n", ntohs(addr.sin_port), G2_IN_PORT); - } if (len == 0) { - printf("DEBUG: Run: read zero bytes from %u\n", ntohs(addr.sin_port)); + fprintf(stderr, "DEBUG: Run: read zero bytes from %u\n", ntohs(addr.sin_port)); continue; } @@ -194,7 +193,7 @@ void CQnetRelay::Run(const char *cfgfile) //printf("read %d bytes from MMDVMHost\n", (int)len); if (ProcessMMDVM(len, buf)) break; - } else if (0 == ::memcmp(buf, "DSTR", 4)) { + } else if (0 == ::memcmp(buf, "DSVT", 4)) { //printf("read %d bytes from MMDVMHost\n", (int)len); if (ProcessGateway(len, buf)) break; @@ -203,12 +202,13 @@ void CQnetRelay::Run(const char *cfgfile) for (int i=0; i<4; i++) title[i] = (buf[i]>=0x20u && buf[i]<0x7fu) ? buf[i] : '.'; title[4] = '\0'; - printf("DEBUG: Run: received unknow packet '%s' len=%d\n", title, (int)len); + fprintf(stderr, "DEBUG: Run: received unknow packet '%s' len=%d\n", title, (int)len); } } ::close(msock); - ::close(gsock); + Gate2Modem.Close(); + return false; } int CQnetRelay::SendTo(const int fd, const unsigned char *buf, const int size, const std::string &address, const unsigned short port) @@ -229,43 +229,49 @@ int CQnetRelay::SendTo(const int fd, const unsigned char *buf, const int size, c bool CQnetRelay::ProcessGateway(const int len, const unsigned char *raw) { - if (29==len || 58==len) { //here is dstar data - SDSTR buf; - ::memcpy(buf.pkt_id, raw, len); // transfer raw data to SDSTR struct + if (27==len || 56==len) { //here is dstar data + SDSVT dsvt; + ::memcpy(dsvt.title, raw, len); // transfer raw data to SDSVT struct - SDSRP pkt; // destination + SDSRP dsrp; // destination // fill in some inital stuff - ::memcpy(pkt.title, "DSRP", 4); - pkt.voice.id = buf.vpkt.streamid; - pkt.voice.seq = buf.vpkt.ctrl; - if (29 == len) { // write an AMBE packet - pkt.tag = 0x21U; - if (pkt.voice.seq & 0x40) -// printf("INFO: ProcessGateway: sending voice end-of-stream\n"); - ; - else if (pkt.voice.seq > 20) - printf("DEBUG: ProcessGateway: unexpected voice sequence number %d\n", pkt.voice.seq); - pkt.voice.err = 0; // NOT SURE WHERE TO GET THIS FROM THE INPUT buf - memcpy(pkt.voice.ambe, buf.vpkt.vasd.voice, 12); - int ret = SendTo(msock, pkt.title, 21, MMDVM_IP, MMDVM_IN_PORT); + ::memcpy(dsrp.title, "DSRP", 4); + dsrp.voice.id = dsvt.streamid; // voice or header is the same position + dsrp.voice.seq = dsvt.ctrl; // ditto + if (27 == len) { // write an AMBE packet + dsrp.tag = 0x21U; + if (log_qso && (dsrp.voice.seq & 0x40)) + printf("Sent DSRP end of streamid=%04x\n", ntohs(dsrp.voice.id)); + if ((dsrp.voice.seq & ~0x40U) > 20) + printf("DEBUG: ProcessGateway: unexpected voice sequence number %d\n", dsrp.voice.seq); + dsrp.voice.err = 0; // NOT SURE WHERE TO GET THIS FROM THE INPUT buf + memcpy(dsrp.voice.ambe, dsvt.vasd.voice, 12); + int ret = SendTo(msock, dsrp.title, 21, MMDVM_IP, MMDVM_IN_PORT); if (ret != 21) { - printf("ERROR: ProcessGateway: Could not write AMBE mmdvm packet\n"); + printf("ERROR: ProcessGateway: Could not write AMBE mmdvmhost packet\n"); return true; } } else { // write a Header packet - pkt.tag = 0x20U; - pkt.header.id = buf.vpkt.streamid; - if (pkt.header.seq) { + dsrp.tag = 0x20U; + if (dsrp.header.seq) { // printf("DEBUG: ProcessGateway: unexpected pkt.header.seq %d, resetting to 0\n", pkt.header.seq); - pkt.header.seq = 0; + dsrp.header.seq = 0; } - memcpy(pkt.header.flag, buf.vpkt.hdr.flag, 41); - int ret = SendTo(msock, pkt.title, 49, MMDVM_IP, MMDVM_IN_PORT); + //memcpy(dsrp.header.flag, dsvt.hdr.flag, 41); + memcpy(dsrp.header.flag, dsvt.hdr.flag, 3); + memcpy(dsrp.header.r1, dsvt.hdr.rpt1, 8); + memcpy(dsrp.header.r2, dsvt.hdr.rpt2, 8); + memcpy(dsrp.header.ur, dsvt.hdr.urcall, 8); + memcpy(dsrp.header.my, dsvt.hdr.mycall, 8); + memcpy(dsrp.header.nm, dsvt.hdr.sfx, 4); + memcpy(dsrp.header.pfcs, dsvt.hdr.pfcs, 2); + int ret = SendTo(msock, dsrp.title, 49, MMDVM_IP, MMDVM_IN_PORT); if (ret != 49) { - printf("ERROR: ProcessGateway: Could not write Header mmdvm packet\n"); + printf("ERROR: ProcessGateway: Could not write Header mmdvmhost packet\n"); return true; } - printf("INFO: ProcessGateway: sent header to port %u pkt = '%s'\n", MMDVM_IN_PORT, std::string((char *)pkt.header.r2, 36).c_str()); + if (log_qso) + printf("Sent DSRP to %u, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", MMDVM_IN_PORT, ntohs(dsrp.header.id), dsrp.header.ur, dsrp.header.r2, dsrp.header.r1, dsrp.header.my, dsrp.header.nm); } } else @@ -275,205 +281,124 @@ bool CQnetRelay::ProcessGateway(const int len, const unsigned char *raw) bool CQnetRelay::ProcessMMDVM(const int len, const unsigned char *raw) { - static short old_id = 0U; - static short stream_id = 0U; - SDSRP mpkt; + static unsigned short id = 0U; + SDSRP dsrp; if (len < 65) - ::memcpy(mpkt.title, raw, len); // transfer raw data to SDSRP struct + ::memcpy(dsrp.title, raw, len); // transfer raw data to SDSRP struct if (49==len || 21==len) { // grab the stream id if this is a header if (49 == len) { - stream_id = mpkt.header.id; - if (old_id == stream_id) + if (dsrp.header.id == id) + return false; + id = dsrp.header.id; + } else { + if (dsrp.voice.id != id) return false; - old_id = stream_id; } - SDSTR gpkt; // destination + SDSVT dsvt; // destination // sets most of the params - ::memcpy(gpkt.pkt_id, "DSTR", 4); - gpkt.counter = COUNTER++; - gpkt.flag[0] = 0x73; - gpkt.flag[1] = 0x12; - gpkt.flag[2] = 0x0; - gpkt.vpkt.icm_id = 0x20; - gpkt.vpkt.dst_rptr_id = 0x0; - gpkt.vpkt.snd_rptr_id = 0x1; - gpkt.vpkt.snd_term_id = ('B'==RPTR_MOD) ? 0x1 : (('C'==RPTR_MOD) ? 0x2 : 0x3); - gpkt.vpkt.streamid = stream_id; + ::memcpy(dsvt.title, "DSVT", 4); + dsvt.config = (len==49) ? 0x10U : 0x20U; + memset(dsvt.flaga, 0U, 3U); + dsvt.id = 0x20U; + dsvt.flagb[0] = 0x0U; + dsvt.flagb[1] = 0x1U; + dsvt.flagb[2] = ('B'==RPTR_MOD) ? 0x1U : (('C'==RPTR_MOD) ? 0x2U : 0x3U); + dsvt.streamid = id; if (49 == len) { // header - gpkt.remaining = 0x30; - gpkt.vpkt.ctrl = 0x80; - ::memcpy(gpkt.vpkt.hdr.flag, mpkt.header.flag, 41); - int ret = SendTo(msock, gpkt.pkt_id, 58, G2_INTERNAL_IP, G2_IN_PORT); - if (ret != 58) { + dsvt.ctrl = 0x80; + //memcpy(dsvt.hdr.flag, dsrp.header.flag, 41); + memcpy(dsvt.hdr.flag, dsrp.header.flag, 3); + memcpy(dsvt.hdr.rpt1, dsrp.header.r1, 8); + memcpy(dsvt.hdr.rpt2, dsrp.header.r2, 8); + memcpy(dsvt.hdr.urcall, dsrp.header.ur, 8); + memcpy(dsvt.hdr.mycall, dsrp.header.my, 8); + memcpy(dsvt.hdr.sfx, dsrp.header.nm, 4); + memcpy(dsvt.hdr.pfcs, dsrp.header.pfcs, 2); + if (56 != Modem2Gate.Write(dsvt.title, 56)) { printf("ERROR: ProcessMMDVM: Could not write gateway header packet\n"); return true; } - printf("INFO: ProcessMMDVM: sent header to port %u pkt = '%s'\n", G2_IN_PORT, std::string((char *)gpkt.vpkt.hdr.r2, 36).c_str()); + if (log_qso) + printf("Sent DSVT streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", ntohs(dsvt.streamid), dsvt.hdr.urcall, dsvt.hdr.rpt1, dsvt.hdr.rpt2, dsvt.hdr.mycall, dsvt.hdr.sfx); } else if (21 == len) { // ambe - gpkt.remaining = 0x16; - gpkt.vpkt.ctrl = mpkt.header.seq; - ::memcpy(gpkt.vpkt.vasd.voice, mpkt.voice.ambe, 12); - int ret = SendTo(msock, gpkt.pkt_id, 29, G2_INTERNAL_IP, G2_IN_PORT); - if (ret != 29) { + dsvt.ctrl = dsrp.header.seq; + memcpy(dsvt.vasd.voice, dsrp.voice.ambe, 12); + + if (27 != Modem2Gate.Write(dsvt.title, 27)) { printf("ERROR: ProcessMMDVM: Could not write gateway voice packet\n"); return true; } + + if (log_qso && dsvt.ctrl&0x40) + printf("Sent DSVT end of streamid=%04x\n", ntohs(dsvt.streamid)); } - } else if (len < 65 && mpkt.tag == 0xAU) { + } else if (len < 65 && dsrp.tag == 0xAU) { // printf("MMDVM Poll: '%s'\n", (char *)mpkt.poll_msg); } else printf("DEBUG: ProcessMMDVM: unusual packet len=%d\n", len); return false; } -bool CQnetRelay::GetValue(const Config &cfg, const char *path, int &value, const int min, const int max, const int default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%d]\n", path, value); - return true; -} - -bool CQnetRelay::GetValue(const Config &cfg, const char *path, double &value, const double min, const double max, const double default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%lg]\n", path, value); - return true; -} - -bool CQnetRelay::GetValue(const Config &cfg, const char *path, bool &value, const bool default_value) -{ - if (! cfg.lookupValue(path, value)) - value = default_value; - printf("%s = [%s]\n", path, value ? "true" : "false"); - return true; -} - -bool CQnetRelay::GetValue(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) -{ - if (cfg.lookupValue(path, value)) { - int l = value.length(); - if (lmax) { - printf("%s value '%s' is wrong size\n", path, value.c_str()); - return false; - } - } else - value = default_value; - printf("%s = [%s]\n", path, value.c_str()); - return true; -} - // process configuration file and return true if there was a problem bool CQnetRelay::ReadConfig(const char *cfgFile) { - Config cfg; - + CQnetConfigure cfg; printf("Reading file %s\n", cfgFile); - // Read the file. If there is an error, report it and exit. - try { - cfg.readFile(cfgFile); - } - catch(const FileIOException &fioex) { - printf("Can't read %s\n", cfgFile); + if (cfg.Initialize(cfgFile)) return true; - } - catch(const ParseException &pex) { - printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); - return true; - } - std::string mmdvm_path, value; - int i; - for (i=0; i<3; i++) { - mmdvm_path = "module."; - mmdvm_path += ('a' + i); - if (cfg.lookupValue(mmdvm_path + ".type", value)) { - if (0 == strcasecmp(value.c_str(), "mmdvm")) + const std::string estr; // an empty GetDefaultString + + std::string mmdvm_path("module_"); + std::string type; + if (0 > assigned_module) { + // we need to find the lone mmdvmhost module + for (int i=0; i<3; i++) { + std::string test(mmdvm_path); + test.append(1, 'a'+i); + if (cfg.KeyExists(test)) { + cfg.GetValue(test, estr, type, 1, 16); + if (type.compare("mmdvmhost")) + continue; // this ain't it! + mmdvm_path.assign(test); + assigned_module = i; break; + } } - } - if (i >= 3) { - printf("mmdvm not defined in any module!\n"); - return true; - } - RPTR_MOD = 'A' + i; - int repeater_module = i; - - if (cfg.lookupValue(std::string(mmdvm_path+".callsign").c_str(), value) || cfg.lookupValue("ircddb.login", value)) { - int l = value.length(); - if (l<3 || l>CALL_SIZE-2) { - printf("Call '%s' is invalid length!\n", value.c_str()); + if (0 > assigned_module) { + fprintf(stderr, "Error: no 'mmdvmhost' module found\n!"); return true; - } else { - for (i=0; iCALL_SIZE-2) { - printf("Call '%s' is invalid length!\n", value.c_str()); - return true; - } else { - for (i=0; i #include -#include - #include -using namespace libconfig; +#include "UnixDgramSocket.h" #define CALL_SIZE 8 #define IP_SIZE 15 @@ -33,9 +31,9 @@ class CQnetRelay { public: // functions - CQnetRelay(); + CQnetRelay(int mod); ~CQnetRelay(); - void Run(const char *cfgfile); + bool Run(const char *cfgfile); // data static std::atomic keep_running; @@ -51,21 +49,21 @@ private: // read configuration file bool ReadConfig(const char *); - bool GetValue(const Config &cfg, const char *path, int &value, const int min, const int max, const int default_value); - bool GetValue(const Config &cfg, const char *path, double &value, const double min, const double max, const double default_value); - bool GetValue(const Config &cfg, const char *path, bool &value, const bool default_value); - bool GetValue(const Config &cfg, const char *path, std::string &value, const int min, const int max, const char *default_value); + + // Unix sockets + int assigned_module; + std::string gate2modem, modem2gate; + CUnixDgramWriter Modem2Gate; + CUnixDgramReader Gate2Modem; // config data char RPTR_MOD; - char RPTR[CALL_SIZE + 1]; - char OWNER[CALL_SIZE + 1]; - std::string MMDVM_IP, G2_INTERNAL_IP; - unsigned short MMDVM_IN_PORT, MMDVM_OUT_PORT, G2_IN_PORT, G2_OUT_PORT; - int WAIT_FOR_PACKETS, DELAY_BEFORE, DELAY_BETWEEN; + std::string MMDVM_IP; + unsigned short MMDVM_IN_PORT, MMDVM_OUT_PORT; + bool log_qso; // parameters - int msock, gsock; + int msock; unsigned int seed; unsigned short COUNTER; }; diff --git a/QnetRemote.cpp b/QnetRemote.cpp index c01894a..67f243c 100644 --- a/QnetRemote.cpp +++ b/QnetRemote.cpp @@ -1,6 +1,6 @@ /* * Copyright (C) 2010 by Scott Lawson KI4LKF - * Copyright (C) 2018 by Thomas A. Early N7TAE + * Copyright (C) 2018-2019 by Thomas A. Early N7TAE * * 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 @@ -32,23 +32,20 @@ #include #include #include -#include #include #include "QnetTypeDefs.h" +#include "Random.h" +#include "QnetConfigure.h" +#include "UnixDgramSocket.h" -using namespace libconfig; - -#define VERSION "v1.0" - -int sockDst = -1; -struct sockaddr_in toDst; +#define VERSION "v2.2" +int module; time_t tNow = 0; short streamid_raw = 0; -bool isdefined[3] = { false, false, false }; -std::string REPEATER, IP_ADDRESS; -int PORT, PLAY_WAIT, PLAY_DELAY; +std::string REPEATER, togateway; +int PLAY_WAIT, PLAY_DELAY; unsigned char silence[9] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8 }; @@ -72,152 +69,52 @@ unsigned short crc_tabccitt[256] = { 0xf78f,0xe606,0xd49d,0xc514,0xb1ab,0xa022,0x92b9,0x8330,0x7bc7,0x6a4e,0x58d5,0x495c,0x3de3,0x2c6a,0x1ef1,0x0f78 }; -void calcPFCS(unsigned char rawbytes[58]) -{ - - unsigned short crc_dstar_ffff = 0xffff; - unsigned short tmp, short_c; - short int i; +CQnetConfigure cfg; - for (i = 17; i < 56 ; i++) { - short_c = 0x00ff & (unsigned short)rawbytes[i]; - tmp = (crc_dstar_ffff & 0x00ff) ^ short_c; - crc_dstar_ffff = (crc_dstar_ffff >> 8) ^ crc_tabccitt[tmp]; - } - crc_dstar_ffff = ~crc_dstar_ffff; - tmp = crc_dstar_ffff; - - rawbytes[56] = (unsigned char)(crc_dstar_ffff & 0xff); - rawbytes[57] = (unsigned char)((tmp >> 8) & 0xff); - return; -} - -bool dst_open(const char *ip, const int port) +bool ReadCfgFile() { - int reuse = 1; + const std::string estr; + std::string type; + std::string path = "module_"; + path.append(1, 'a'+module); - sockDst = socket(PF_INET,SOCK_DGRAM,0); - if (sockDst == -1) { - printf("Failed to create DSTAR socket\n"); - return true; - } - if (setsockopt(sockDst,SOL_SOCKET,SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { - close(sockDst); - sockDst = -1; - printf("setsockopt DSTAR REUSE failed\n"); + if (! cfg.KeyExists(path)) { + fprintf(stderr, "%s not defined!\n", path.c_str()); return true; } - memset(&toDst,0,sizeof(struct sockaddr_in)); - toDst.sin_family = AF_INET; - toDst.sin_port = htons(port); - toDst.sin_addr.s_addr = inet_addr(ip); + cfg.GetValue(path, estr, type, 1, 16); - fcntl(sockDst,F_SETFL,O_NONBLOCK); - return false; -} - -void dst_close() -{ - if (sockDst != -1) { - close(sockDst); - sockDst = -1; + cfg.GetValue(path+"_callsign", type, REPEATER, 0, 6); + if (REPEATER.length() < 4) { + if (cfg.GetValue("ircddb_login", estr, REPEATER, 3, 6)) { + fprintf(stderr, "no Callsign for the repeater was found!\n"); + return true; + } } - return; -} - -bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%d]\n", path, value); - return true; -} + cfg.GetValue("gateway_modem2gate", estr, togateway, 1, FILENAME_MAX); -bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%lg]\n", path, value); - return true; -} - -bool get_value(const Config &cfg, const char *path, bool &value, bool default_value) -{ - if (! cfg.lookupValue(path, value)) - value = default_value; - printf("%s = [%s]\n", path, value ? "true" : "false"); - return true; -} - -bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) -{ - if (cfg.lookupValue(path, value)) { - int l = value.length(); - if (lmax) { - printf("%s is invalid\n", path); - return false; - } - } else - value = default_value; - printf("%s = [%s]\n", path, value.c_str()); - return true; + cfg.GetValue("timing_play_wait", estr, PLAY_WAIT, 1, 10); + cfg.GetValue("timing_play_delay", estr, PLAY_DELAY, 15, 25); + return false; } -/* process configuration file */ -bool read_config(const char *cfgFile) +void calcPFCS(unsigned char rawbytes[56]) { - Config cfg; - - printf("Reading file %s\n", cfgFile); - // Read the file. If there is an error, report it and exit. - try { - cfg.readFile(cfgFile); - } catch(const FileIOException &fioex) { - printf("Can't read %s\n", cfgFile); - return true; - } catch(const ParseException &pex) { - printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); - return true; - } + unsigned short crc_dstar_ffff = 0xffff; + unsigned short tmp, short_c; + short int i; - if (! get_value(cfg, "ircddb.login", REPEATER, 3, 6, "UNDEFINED")) - return true; - REPEATER.resize(6, ' '); - printf("REPEATER=[%s]\n", REPEATER.c_str()); - - for (short int m=0; m<3; m++) { - std::string path = "module."; - path += m + 'a'; - std::string type; - if (cfg.lookupValue(std::string(path+".type").c_str(), type)) { - if (strcasecmp(type.c_str(), "dvap") && strcasecmp(type.c_str(), "dvrptr") && strcasecmp(type.c_str(), "mmdvm")) { - printf("module type '%s' is invalid\n", type.c_str()); - return true; - } - isdefined[m] = true; - } - } - if (false==isdefined[0] && false==isdefined[1] && false==isdefined[2]) { - printf("No repeaters defined!\n"); - return true; + for (i = 15; i < 54 ; i++) { + short_c = 0x00ff & (unsigned short)rawbytes[i]; + tmp = (crc_dstar_ffff & 0x00ff) ^ short_c; + crc_dstar_ffff = (crc_dstar_ffff >> 8) ^ crc_tabccitt[tmp]; } + crc_dstar_ffff = ~crc_dstar_ffff; + tmp = crc_dstar_ffff; - if (! get_value(cfg, "gateway.internal.ip", IP_ADDRESS, 7, 15, "127.0.0.1")) - return true; - - get_value(cfg, "gateway.internal.port", PORT, 16000, 65535, 19000); - - get_value(cfg, "timing.play.wait", PLAY_WAIT, 1, 10, 2); - - get_value(cfg, "timing.play.delay", PLAY_DELAY, 9, 25, 19); - - return false; + rawbytes[54] = (unsigned char)(crc_dstar_ffff & 0xff); + rawbytes[55] = (unsigned char)((tmp >> 8) & 0xff); + return; } void ToUpper(std::string &str) @@ -229,21 +126,43 @@ void ToUpper(std::string &str) int main(int argc, char *argv[]) { - unsigned short G2_COUNTER = 0; - if (argc != 4) { - printf("Usage: %s \n", argv[0]); - printf("Example: %s c n7tae xrf757cl\n", argv[0]); - printf("Where...\n"); - printf(" c is the local repeater module\n"); - printf(" n7tae is the value of mycall\n"); - printf(" xrf757cl is the value of yourcall, in this case this is a Link command\n\n"); + fprintf(stderr, "Usage: %s \n", argv[0]); + fprintf(stderr, "Example: %s c n7tae xrf757al\n", argv[0]); + fprintf(stderr, "Where...\n"); + fprintf(stderr, " c is the local repeater module\n"); + fprintf(stderr, " n7tae is the value of mycall\n"); + fprintf(stderr, " xrf757al is the value of yourcall, in this case this is a Link command\n\n"); return 0; } + switch (argv[1][0]) { + case '0': + case 'a': + case 'A': + module = 0; + break; + case '1': + case 'b': + case 'B': + module = 1; + break; + case '2': + case 'c': + case 'C': + module = 2; + break; + default: + fprintf(stderr, "module must be 0, a, A, 1, b, B, 2, c or C, not %s\n", argv[1]); + return 1; + } + std::string cfgfile(CFG_DIR); cfgfile += "/qn.cfg"; - if (read_config(cfgfile.c_str())) + if (cfg.Initialize(cfgfile.c_str())) + return 1; + + if (ReadCfgFile()) return 1; if (REPEATER.size() > 6) { @@ -252,14 +171,6 @@ int main(int argc, char *argv[]) } ToUpper(REPEATER); - char module = argv[1][0]; - if (islower(module)) - module = toupper(module); - if ((module != 'A') && (module != 'B') && (module != 'C')) { - printf("module must be one of A B C\n"); - return 1; - } - if (strlen(argv[2]) > 8) { printf("MYCALL can not be more than 8 characters, %s is invalid\n", argv[2]); return 1; @@ -283,127 +194,118 @@ int main(int argc, char *argv[]) RADIO_ID.resize(20, ' '); time(&tNow); - srand(tNow + getpid()); - - if (dst_open(IP_ADDRESS.c_str(), PORT)) - return 1; - - SDSTR pkt; - memcpy(pkt.pkt_id,"DSTR", 4); - pkt.counter = htons(G2_COUNTER); - pkt.flag[0] = 0x73; - pkt.flag[1] = 0x12; - pkt.flag[2] = 0x00; - pkt.remaining = 0x30; - pkt.vpkt.icm_id = 0x20; - pkt.vpkt.dst_rptr_id = 0x00; - pkt.vpkt.snd_rptr_id = 0x01; - if (module == 'A') - pkt.vpkt.snd_term_id = 0x03; - else if (module == 'B') - pkt.vpkt.snd_term_id = 0x01; - else if (module == 'C') - pkt.vpkt.snd_term_id = 0x02; + CRandom Random; + CUnixDgramWriter ToGateway; + ToGateway.SetUp(togateway.c_str()); + + SDSVT pkt; + memcpy(pkt.title, "DSVT", 4); + pkt.config = 0x10U; + memset(pkt.flaga, 0U, 3U); + pkt.id = 0x20U; + pkt.flagb[0] = 0x0U; + pkt.flagb[1] = 0x1U; + if (module == 0) + pkt.flagb[2] = 0x3U; + else if (module == 1) + pkt.flagb[2] = 0x1U; + else if (module == 2) + pkt.flagb[2] = 0x2U; else - pkt.vpkt.snd_term_id = 0x00; - streamid_raw = (unsigned short)(::rand() & 0xFFFF); - pkt.vpkt.streamid = htons(streamid_raw); - pkt.vpkt.ctrl = 0x80; - pkt.vpkt.hdr.flag[0] = pkt.vpkt.hdr.flag[1] = pkt.vpkt.hdr.flag[2] = 0x00; + pkt.flagb[3] = 0x0U; + streamid_raw = Random.NewStreamID(); + pkt.streamid = htons(streamid_raw); + pkt.ctrl = 0x80; + pkt.hdr.flag[0] = pkt.hdr.flag[1] = pkt.hdr.flag[2] = 0x00; REPEATER.resize(7, ' '); - memcpy(pkt.vpkt.hdr.r2, std::string(REPEATER + 'G').c_str(), 8); - memcpy(pkt.vpkt.hdr.r1, std::string(REPEATER + module).c_str(), 8); + memcpy(pkt.hdr.rpt2, REPEATER.c_str(), 8); + pkt.hdr.rpt2[7] = 'G'; + memcpy(pkt.hdr.rpt1, REPEATER.c_str(), 8); + pkt.hdr.rpt1[7] = 'A' + module; mycall.resize(8, ' '); - memcpy(pkt.vpkt.hdr.my, mycall.c_str(), 8); - memcpy(pkt.vpkt.hdr.nm, "QNET", 4); + memcpy(pkt.hdr.mycall, mycall.c_str(), 8); + memcpy(pkt.hdr.sfx, "QNET", 4); if (yourcall.size() < 3) yourcall = std::string(8-yourcall.size(), ' ') + yourcall; // right justify 1 or 2 letter commands else yourcall.resize(8, ' '); - memcpy(pkt.vpkt.hdr.ur, yourcall.c_str(), 8); + memcpy(pkt.hdr.urcall, yourcall.c_str(), 8); - calcPFCS(pkt.pkt_id); + calcPFCS(pkt.title); // send the header - int sent = sendto(sockDst, pkt.pkt_id, 58, 0, (struct sockaddr *)&toDst, sizeof(toDst)); - if (sent != 58) { + if (56 != ToGateway.Write(pkt.title, 56)) { printf("%s: ERROR: Couldn't send header!\n", argv[0]); - dst_close(); return 1; } // prepare and send 10 voice packets - pkt.remaining = 0x13; - memcpy(pkt.vpkt.vasd.voice, silence, 9); + pkt.config = 0x20U; + memcpy(pkt.vasd.voice, silence, 9); for (int i=0; i<10; i++) { - usleep(delay); - /* start sending silence + text */ - pkt.counter = htons(++G2_COUNTER); - pkt.vpkt.ctrl = i; + pkt.ctrl = i; switch (i) { case 0: // sync voice frame - pkt.vpkt.vasd.text[0] = 0x55; - pkt.vpkt.vasd.text[1] = 0x2d; - pkt.vpkt.vasd.text[2] = 0x16; + pkt.vasd.text[0] = 0x55; + pkt.vasd.text[1] = 0x2d; + pkt.vasd.text[2] = 0x16; break; case 1: - pkt.vpkt.vasd.text[0] = '@' ^ 0x70; - pkt.vpkt.vasd.text[1] = RADIO_ID[0] ^ 0x4f; - pkt.vpkt.vasd.text[2] = RADIO_ID[1] ^ 0x93; + pkt.vasd.text[0] = '@' ^ 0x70; + pkt.vasd.text[1] = RADIO_ID[0] ^ 0x4f; + pkt.vasd.text[2] = RADIO_ID[1] ^ 0x93; break; case 2: - pkt.vpkt.vasd.text[0] = RADIO_ID[2] ^ 0x70; - pkt.vpkt.vasd.text[1] = RADIO_ID[3] ^ 0x4f; - pkt.vpkt.vasd.text[2] = RADIO_ID[4] ^ 0x93; + pkt.vasd.text[0] = RADIO_ID[2] ^ 0x70; + pkt.vasd.text[1] = RADIO_ID[3] ^ 0x4f; + pkt.vasd.text[2] = RADIO_ID[4] ^ 0x93; break; case 3: - pkt.vpkt.vasd.text[0] = 'A' ^ 0x70; - pkt.vpkt.vasd.text[1] = RADIO_ID[5] ^ 0x4f; - pkt.vpkt.vasd.text[2] = RADIO_ID[6] ^ 0x93; + pkt.vasd.text[0] = 'A' ^ 0x70; + pkt.vasd.text[1] = RADIO_ID[5] ^ 0x4f; + pkt.vasd.text[2] = RADIO_ID[6] ^ 0x93; break; case 4: - pkt.vpkt.vasd.text[0] = RADIO_ID[7] ^ 0x70; - pkt.vpkt.vasd.text[1] = RADIO_ID[8] ^ 0x4f; - pkt.vpkt.vasd.text[2] = RADIO_ID[9] ^ 0x93; + pkt.vasd.text[0] = RADIO_ID[7] ^ 0x70; + pkt.vasd.text[1] = RADIO_ID[8] ^ 0x4f; + pkt.vasd.text[2] = RADIO_ID[9] ^ 0x93; break; case 5: - pkt.vpkt.vasd.text[0] = 'B' ^ 0x70; - pkt.vpkt.vasd.text[1] = RADIO_ID[10] ^ 0x4f; - pkt.vpkt.vasd.text[2] = RADIO_ID[11] ^ 0x93; + pkt.vasd.text[0] = 'B' ^ 0x70; + pkt.vasd.text[1] = RADIO_ID[10] ^ 0x4f; + pkt.vasd.text[2] = RADIO_ID[11] ^ 0x93; break; case 6: - pkt.vpkt.vasd.text[0] = RADIO_ID[12] ^ 0x70; - pkt.vpkt.vasd.text[1] = RADIO_ID[13] ^ 0x4f; - pkt.vpkt.vasd.text[2] = RADIO_ID[14] ^ 0x93; + pkt.vasd.text[0] = RADIO_ID[12] ^ 0x70; + pkt.vasd.text[1] = RADIO_ID[13] ^ 0x4f; + pkt.vasd.text[2] = RADIO_ID[14] ^ 0x93; break; case 7: - pkt.vpkt.vasd.text[0] = 'C' ^ 0x70; - pkt.vpkt.vasd.text[1] = RADIO_ID[15] ^ 0x4f; - pkt.vpkt.vasd.text[2] = RADIO_ID[16] ^ 0x93; + pkt.vasd.text[0] = 'C' ^ 0x70; + pkt.vasd.text[1] = RADIO_ID[15] ^ 0x4f; + pkt.vasd.text[2] = RADIO_ID[16] ^ 0x93; break; case 8: - pkt.vpkt.vasd.text[0] = RADIO_ID[17] ^ 0x70; - pkt.vpkt.vasd.text[1] = RADIO_ID[18] ^ 0x4f; - pkt.vpkt.vasd.text[2] = RADIO_ID[19] ^ 0x93; + pkt.vasd.text[0] = RADIO_ID[17] ^ 0x70; + pkt.vasd.text[1] = RADIO_ID[18] ^ 0x4f; + pkt.vasd.text[2] = RADIO_ID[19] ^ 0x93; break; case 9: // terminal voice packet - pkt.vpkt.ctrl |= 0x40; - pkt.vpkt.vasd.text[0] = 0x70; - pkt.vpkt.vasd.text[1] = 0x4f; - pkt.vpkt.vasd.text[2] = 0x93; + pkt.ctrl |= 0x40; + pkt.vasd.text[0] = 0x70; + pkt.vasd.text[1] = 0x4f; + pkt.vasd.text[2] = 0x93; break; } - sent = sendto(sockDst,pkt.pkt_id, 29, 0, (struct sockaddr *)&toDst, sizeof(toDst)); - if (sent != 29) { + if (27 != ToGateway.Write(pkt.title, 27)) { printf("%s: ERROR: could not send voice packet %d\n", argv[0], i); - dst_close(); return 1; } + usleep(delay); } - dst_close(); return 0; } diff --git a/QnetTypeDefs.h b/QnetTypeDefs.h index 8c85972..02a0bbc 100644 --- a/QnetTypeDefs.h +++ b/QnetTypeDefs.h @@ -1,6 +1,6 @@ #pragma once /* - * Copyright 2017,2018 by Thomas Early, N7TAE + * Copyright 2017-2019 by Thomas Early, N7TAE * * 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 @@ -18,8 +18,8 @@ */ // for communicating with the g2 gateway on the internal port -#pragma pack(push, 1) // we need to be sure these structures don't have any dead space -typedef struct pkt_tag { +#pragma pack(push, 1) // used internally by Icom stacks +typedef struct dstr_tag { unsigned char pkt_id[4]; // 0 "DSTR" unsigned short counter; // 4 unsigned char flag[3]; // 6 { 0x73, 0x12, 0x00 } @@ -63,16 +63,16 @@ typedef struct pkt_tag { } SDSTR; #pragma pack(pop) -// for the g2 external port +// for the g2 external port and between QnetGateway programs #pragma pack(push, 1) typedef struct dsvt_tag { unsigned char title[4]; // 0 "DSVT" unsigned char config; // 4 0x10 is hdr 0x20 is vasd unsigned char flaga[3]; // 5 zeros unsigned char id; // 8 0x20 - unsigned char flagb[3]; // 9 0x0 0x1 0x1 + unsigned char flagb[3]; // 9 0x0 0x1 (A:0x3 B:0x1 C:0x2) unsigned short streamid;// 12 - unsigned char counter; // 14 hdr: 0x80 vsad: framecounter (mod 21) + unsigned char ctrl; // 14 hdr: 0x80 vsad: framecounter (mod 21) union { struct { // index unsigned char flag[3]; // 15 @@ -93,7 +93,7 @@ typedef struct dsvt_tag { // for mmdvm #pragma pack(push, 1) -typedef struct mmdvm_tag { // offset size +typedef struct dsrp_tag { // offset size unsigned char title[4]; // "DSRP" 0 unsigned char tag; // Poll : 0xA 4 // Header : busy ? 0x22 : 0x20 @@ -108,10 +108,10 @@ typedef struct mmdvm_tag { // offset size // 0x01 Dstar Relay Unavailable unsigned char r2[8]; // Repeater 2 11 unsigned char r1[8]; // Repeater 1 19 - unsigned char yr[8]; // Your Call 27 + unsigned char ur[8]; // Your Call 27 unsigned char my[8]; // My Call 35 unsigned char nm[4]; // Name 43 - unsigned short pfcs; // checksum 47 49 + unsigned char pfcs[2]; // checksum 47 49 } header; struct { unsigned short id; // random id number 5 @@ -123,3 +123,10 @@ typedef struct mmdvm_tag { // offset size }; } SDSRP; #pragma pack(pop) + +#pragma pack(push, 1) +typedef struct link_family_tag { + char title[4]; + int family[3]; +} SLINKFAMILY; +#pragma pack(pop) diff --git a/QnetVoice.cpp b/QnetVoice.cpp index 2f14134..e78c3a8 100644 --- a/QnetVoice.cpp +++ b/QnetVoice.cpp @@ -18,201 +18,42 @@ */ #include -#include #include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include #include -#include +#include "QnetConfigure.h" -#include "QnetTypeDefs.h" - -using namespace libconfig; - -#define VERSION "v3.1" - -int sockDst = -1; -struct sockaddr_in toDst; -FILE *fp = NULL; -time_t tNow = 0; -short streamid_raw = 0; -int moduleport[3] = { 0, 0, 0 }; -std::string REPEATER, IP_ADDRESS; -int PORT, PLAY_WAIT, PLAY_DELAY; - -unsigned short crc_tabccitt[256] = { - 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 -}; - - - -void calcPFCS(unsigned char rawbytes[58]) -{ - unsigned short crc_dstar_ffff = 0xffff; - unsigned short tmp, short_c; - short int i; - - for (i = 17; i < 56 ; i++) { - short_c = 0x00ff & (unsigned short)rawbytes[i]; - tmp = (crc_dstar_ffff & 0x00ff) ^ short_c; - crc_dstar_ffff = (crc_dstar_ffff >> 8) ^ crc_tabccitt[tmp]; - } - crc_dstar_ffff = ~crc_dstar_ffff; - tmp = crc_dstar_ffff; - - rawbytes[56] = (unsigned char)(crc_dstar_ffff & 0xff); - rawbytes[57] = (unsigned char)((tmp >> 8) & 0xff); - return; - -} - -bool dst_open(const char *ip, const int port) -{ - int reuse = 1; - - sockDst = socket(PF_INET,SOCK_DGRAM,0); - if (sockDst == -1) { - printf("Failed to create DSTAR socket\n"); - return true; - } - if (setsockopt(sockDst,SOL_SOCKET,SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { - close(sockDst); - sockDst = -1; - printf("setsockopt DSTAR REUSE failed\n"); - return true; - } - memset(&toDst,0,sizeof(struct sockaddr_in)); - toDst.sin_family = AF_INET; - toDst.sin_port = htons(port); - toDst.sin_addr.s_addr = inet_addr(ip); - - fcntl(sockDst,F_SETFL,O_NONBLOCK); - return false; -} - -void dst_close() -{ - if (sockDst != -1) { - close(sockDst); - sockDst = -1; - } - return; -} - -bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%d]\n", path, value); - return true; -} - -bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%lg]\n", path, value); - return true; -} - -bool get_value(const Config &cfg, const char *path, bool &value, bool default_value) -{ - if (! cfg.lookupValue(path, value)) - value = default_value; - printf("%s = [%s]\n", path, value ? "true" : "false"); - return true; -} - -bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) -{ - if (cfg.lookupValue(path, value)) { - int l = value.length(); - if (lmax) { - printf("%s is invalid\n", path); - return false; - } - } else - value = default_value; - printf("%s = [%s]\n", path, value.c_str()); - return true; -} +bool isamod[3] = { false, false, false }; +std::string announce_dir; +std::string qnvoice_file; /* process configuration file */ bool read_config(const char *cfgFile) { - Config cfg; + CQnetConfigure cfg; printf("Reading file %s\n", cfgFile); - // Read the file. If there is an error, report it and exit. - try { - cfg.readFile(cfgFile); - } catch(const FileIOException &fioex) { - printf("Can't read %s\n", cfgFile); - return true; - } catch(const ParseException &pex) { - printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); - return true; - } - - if (! get_value(cfg, "ircddb.login", REPEATER, 3, 6, "UNDEFINED")) + if (cfg.Initialize(cfgFile)) return true; - REPEATER.resize(6, ' '); - printf("REPEATER=[%s]\n", REPEATER.c_str()); - for (short int m=0; m<3; m++) { - std::string path = "module."; - path += m + 'a'; + for (int m=0; m<3; m++) { + std::string path("module_"); + path.append(std::to_string(m)); std::string type; - if (cfg.lookupValue(std::string(path+".type").c_str(), type)) { - if (strcasecmp(type.c_str(), "dvap") && strcasecmp(type.c_str(), "dvrptr") && strcasecmp(type.c_str(), "mmdvm")) { + if (cfg.KeyExists(path)) { + cfg.GetValue(path, "", type, 1, 16); + if (strcasecmp(type.c_str(), "dvap") && strcasecmp(type.c_str(), "dvrptr") && strcasecmp(type.c_str(), "mmdvm") && strcasecmp(type.c_str(), "itap")) { printf("module type '%s' is invalid\n", type.c_str()); return true; } - get_value(cfg, std::string(path+".port").c_str(), moduleport[m], 1000, 65535, 19998+m); + isamod[m] = true; } } - if (0==moduleport[0] && 0==moduleport[1] && 0==moduleport[2]) { - printf("No repeaters defined!\n"); - return true; - } - - if (! get_value(cfg, "gateway.internal.ip", IP_ADDRESS, 7, 15, "127.0.0.1")) - return true; - - get_value(cfg, "timing.play.wait", PLAY_WAIT, 1, 10, 2); - get_value(cfg, "timing.play.delay", PLAY_DELAY, 9, 25, 19); + std::string path("file_"); + cfg.GetValue(path+"announce_dir", "", announce_dir, 2, FILENAME_MAX); + cfg.GetValue(path+"qnvoice_file", "", qnvoice_file, 2, FILENAME_MAX); return false; } @@ -226,35 +67,24 @@ void ToUpper(std::string &str) int main(int argc, char *argv[]) { - unsigned short rlen = 0; - static unsigned short G2_COUNTER = 0; - size_t nread = 0; - SDSVT dsvt; - SDSTR dstr; char RADIO_ID[21]; - short int TEXT_idx = 0; if (argc != 4) { - printf("Usage: %s \n", argv[0]); + printf("Usage: %s \n", argv[0]); printf("Where...\n"); - printf(" module is one of your modules\n"); - printf(" mycall is your personal callsign\n"); - printf(" dvtoolFile is a dvtool file\n"); + printf(" is one of your modules: A, B or C\n"); + printf(" is an installed voice file in the configured\n"); + printf(" directory, for example \"unlinked.dat\"\n"); + printf(" is an up to 20-character text message\n"); return 0; } + char module = argv[1][0]; std::string cfgfile(CFG_DIR); cfgfile += "/qn.cfg"; if (read_config(cfgfile.c_str())) return 1; - if (REPEATER.size() > 6) { - printf("repeaterCallsign can not be more than 6 characters, %s is invalid\n", REPEATER.c_str()); - return 1; - } - ToUpper(REPEATER); - - char module = argv[1][0]; if (islower(module)) module = toupper(module); if ((module != 'A') && (module != 'B') && (module != 'C')) { @@ -262,176 +92,33 @@ int main(int argc, char *argv[]) return 1; } - PORT = moduleport[module - 'A']; - if (0 == PORT) { - printf("module %c has no port defined!\n", module); - return 1; - } - - if (strlen(argv[2]) > 8) { - printf("MYCALL can not be more than 8 characters, %s is invalid\n", argv[2]); - return 1; - } - std::string mycall(argv[2]); - ToUpper(mycall); + char pathname[FILENAME_MAX]; + snprintf(pathname, FILENAME_MAX, "%s/%s", announce_dir.c_str(), argv[2]); - fp = fopen(argv[3], "rb"); + FILE *fp = fopen(pathname, "rb"); if (!fp) { - printf("Failed to open file %s for reading\n", argv[3]); - return 1; - } - - /* DVTOOL + 4 byte num_of_records */ - unsigned char buf[10]; - nread = fread(buf, 10, 1, fp); - if (nread != 1) { - printf("Cant read first 10 bytes\n"); - fclose(fp); - return 1; - } - if (0 != memcmp(buf, "DVTOOL", 6)) { - printf("DVTOOL signature not found in %s\n", argv[3]); - fclose(fp); + printf("Failed to find file %s for reading\n", pathname); return 1; } + fclose(fp); - memset(RADIO_ID, ' ', 20); + memset(RADIO_ID, '_', 20); RADIO_ID[20] = '\0'; - memcpy(RADIO_ID, "QnetVoice AMBE Data", 19); - - unsigned long int delay = PLAY_DELAY * 1000L; - sleep(PLAY_WAIT); + unsigned int len = strlen(argv[3]); + strncpy(RADIO_ID, argv[3], len > 20 ? 20 : len); + for (int i=0; i<20; i++) + if (isspace(RADIO_ID[i])) + RADIO_ID[i] = '_'; - time(&tNow); - srand(tNow + getpid()); - - if (dst_open(IP_ADDRESS.c_str(), PORT)) + fp = fopen(qnvoice_file.c_str(), "w"); + if (fp) { + fprintf(fp, "%c_%s_%s\n", module, argv[2], RADIO_ID); + fclose(fp); + } else { + printf("Failed to open %s for writing", qnvoice_file.c_str()); return 1; - - // Read and reformat and write packets - while (true) { - /* 2 byte length */ - nread = fread(&rlen, 2, 1, fp); - if (nread != 1) { - printf("End-Of-File\n"); - break; - } - if (rlen == 56) - streamid_raw = (short)(::rand() & 0xFFFF); - else if (rlen == 27) - ; - else { - printf("Wrong packet size!\n"); - return 1; - } - - /* read the packet */ - nread = fread(dsvt.title, rlen, 1, fp); - printf("Read %d byte packet from %s\n", (int)nread*rlen, argv[3]); - if (rlen == 56) - printf("rpt1=%.8s rpt2=%.8s urcall=%.8s, mycall=%.8s, sfx=%.4s\n", - dsvt.hdr.rpt1, dsvt.hdr.rpt2, dsvt.hdr.urcall, dsvt.hdr.mycall, dsvt.hdr.sfx); - else - printf("streamid=%04X counter=%02X\n", dsvt.streamid, dsvt.counter); - if (nread == 1) { - if (memcmp(dsvt.title, "DSVT", 4) != 0) { - printf("DVST title not found\n"); - return 1; - } - - if (dsvt.id != 0x20) { - printf("Not Voice type\n"); - return 1; - } - - if (dsvt.config!=0x10 && dsvt.config!=0x20) { - printf("Not a valid record type\n"); - return 1; - } - - dstr.counter = htons(G2_COUNTER++); - if (rlen == 56) { - memcpy(dstr.pkt_id, "DSTR", 4); - dstr.flag[0] = 0x73; - dstr.flag[1] = 0x12; - dstr.flag[2] = 0x00; - dstr.remaining = 0x30; - dstr.vpkt.icm_id = 0x20; - dstr.vpkt.dst_rptr_id = dsvt.flagb[0]; - dstr.vpkt.snd_rptr_id = dsvt.flagb[1]; - dstr.vpkt.snd_term_id = dsvt.flagb[2]; - dstr.vpkt.streamid = htons(streamid_raw); - dstr.vpkt.ctrl = dsvt.counter; - for (int i=0; i<3; i++) - dstr.vpkt.hdr.flag[i] = dsvt.hdr.flag[i]; - memset(dstr.vpkt.hdr.r2, ' ', 36); - memcpy(dstr.vpkt.hdr.r2, REPEATER.c_str(), REPEATER.size()); - dstr.vpkt.hdr.r1[7] = 'G'; - memcpy(dstr.vpkt.hdr.r1, REPEATER.c_str(), REPEATER.size()); - dstr.vpkt.hdr.r2[7] = module; - memcpy(dstr.vpkt.hdr.ur, "CQCQCQ", 6); /* yrcall */ - memcpy(dstr.vpkt.hdr.my, mycall.c_str(), mycall.size()); - memcpy(dstr.vpkt.hdr.nm, "QNET", 4); - calcPFCS(dstr.pkt_id); - } else { - dstr.remaining = 0x13; - dstr.vpkt.ctrl = dsvt.counter; - memcpy(dstr.vpkt.vasd.voice, dsvt.vasd.voice, 12); - - if ((dstr.vpkt.vasd.text[0] != 0x55) || (dstr.vpkt.vasd.text[1] != 0x2d) || (dstr.vpkt.vasd.text[2] != 0x16)) { - if (TEXT_idx == 0) { - dstr.vpkt.vasd.text[0] = '@' ^ 0x70; - dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; - } else if (TEXT_idx == 2) { - dstr.vpkt.vasd.text[0] = RADIO_ID[TEXT_idx++] ^ 0x70; - dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; - } else if (TEXT_idx == 5) { - dstr.vpkt.vasd.text[0] = 'A' ^ 0x70; - dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; - } else if (TEXT_idx == 7) { - dstr.vpkt.vasd.text[0] = RADIO_ID[TEXT_idx++] ^ 0x70; - dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; - } else if (TEXT_idx == 10) { - dstr.vpkt.vasd.text[0] = 'B' ^ 0x70; - dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; - } else if (TEXT_idx == 12) { - dstr.vpkt.vasd.text[0] = RADIO_ID[TEXT_idx++] ^ 0x70; - dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; - } else if (TEXT_idx == 15) { - dstr.vpkt.vasd.text[0] = 'C' ^ 0x70; - dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; - } else if (TEXT_idx == 17) { - dstr.vpkt.vasd.text[0] = RADIO_ID[TEXT_idx++] ^ 0x70; - dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; - } else { - dstr.vpkt.vasd.text[0] = 0x70; - dstr.vpkt.vasd.text[1] = 0x4f; - dstr.vpkt.vasd.text[2] = 0x93; - } - } - } - - int sent = sendto(sockDst, dstr.pkt_id, rlen + 2,0, (struct sockaddr *)&toDst, sizeof(toDst)); - if (sent == 58) - printf("Sent DSTR HDR r2=%.8s r1=%.8s ur=%.8s my=%.8s nm=%.4s\n", - dstr.vpkt.hdr.r2, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.ur, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm); - else if (sent == 29) - printf("Sent DSTR DATA streamid=%04X, ctrl=%02X\n", dstr.vpkt.streamid, dstr.vpkt.ctrl); - else - printf("ERROR: sendto returned %d!\n", sent); - } - usleep(delay); } - dst_close(); - fclose(fp); + return 0; } diff --git a/README.md b/README.md index c479291..2dbe10d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,17 @@ QnetGateway =========== -The QnetGateway is an D-Star IRCDDB gateway application that supports MMDVMHost (and all of its supported repeater modems) as well as the DVAP Dongle and the DVRPTR_V1. It is *incredibly easy* to build and install the system. +The QnetGateway is an D-Star IRCDDB gateway application that supports MMDVMHost (and all of its supported repeater modems) as well as the DVAP Dongle, the DVRPTR_V1. It is *incredibly easy* to build and install the system. -For building a QnetGateway + MMDVMHost system, see the MMDVM.README file. To build QnetGateway that uses a DVAP Dongle or DVRPTR V1, see the BUILDING file. +QnetGateway is dual-stack capabile. This means it can simultaneously connect to rr.openquad.net, which is IPv4 based (using 32-bit internet addresses) and to rrv6.openquad.net which is IPv6 based (using 128-bit internet address). If your hot-spot/reapeater has IPv6 access you can enable dual-stack operation (it's IPv4-only by default) and then take advantage of direct world-routable address. The potential benefit of IPv6 to routing is significant. -To get started, clone the software to your Linux device: +The Qnet Gateway program includes support for Icom's new Terminal Mode and Access Point mode. For more information, Terminal Mode turns off the RF portion of you radio and just uses the AMBE vocoder to convert between audio and AMBE data and then sends and receives that data through a USB serial cable. Access Point mode turns your Icom radio into a high power, simplex hot-spot. + +QnetGateway supports MMDVM modems directly, without the need for MMDVMHost. This is for hams that want to use their MMDVM devices and create a hot-spot for D-Star mode only. (You still can talk to your friends on other modes by gathering at multi-mode reflectors, like the QuadNet Array!) + +For building a QnetGateway + MMDVMHost system, see the MMDVM.README file. To build QnetGateway that uses a DVAP Dongle or DVRPTR V1, see the CONFIG+INSTALL file. To build QnetGateway for an Icom Repeater Stack, I have another repo at QnetIcomGateway. Detailed information is available there. + +To get started with an MMDVM-modem, DVAP, DVRPTR or Icom Terminal and/or Access Point system, clone this software to your Linux device: ``` git clone git://github.com/n7tae/QnetGateway.git @@ -15,11 +21,12 @@ Then look to the MMDVM.README or the BUILDING file for more information. QnetGateway includes a "remote control" program, called `qnremote`. After you build and install the system, type `qnremote` for a prompt on how to use it. Using this and cron, it's possible to setup schedules where you system will automatically link up to a reflector, or subscribe to a Routing Group. For More information, see DTMF+REMOTE.README. -For other details of interesting things QnetGatway can do, see the CONFIGURING file. For example, with QnetGateway, you can execute up to 36 different Linux scripts from you radio. Two scripts are include: +For other details of interesting things QnetGatway can do, see the OPERATING file. For example, with QnetGateway, you can execute up to 36 different Linux scripts from you radio. Two scripts are include: ``` YourCall = " HX" will halt your system. YourCall = " RX" will reboot your system. +YourCall - " GX" will restart QnetGateway ``` QnetGateway 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. QnetGateway 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 LICENSE file for more details. diff --git a/Random.h b/Random.h new file mode 100644 index 0000000..d1b2cd4 --- /dev/null +++ b/Random.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018-2019 by Thomas A. Early N7TAE + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +#include +#include + +class CRandom +{ +public: + CRandom() { srandom(getpid()); } + + ~CRandom() {} + + unsigned short NewStreamID() + { + unsigned short r = 0; + while (0 == r) + r = 0xffffU & random(); + return r; + } +}; diff --git a/SEcho.h b/SEcho.h new file mode 100644 index 0000000..5594ff8 --- /dev/null +++ b/SEcho.h @@ -0,0 +1,28 @@ +#pragma once +/* + * Copyright 2018-2019 by Thomas Early, N7TAE + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +typedef struct echo_tag { + bool is_linked; + time_t last_time; + unsigned short streamid; + int fd; + char message[24]; + SDSVT header; // only used in qnlink (qngateway writes the header to the file) + char file[FILENAME_MAX + 1]; +} SECHO; diff --git a/SockAddress.h b/SockAddress.h new file mode 100644 index 0000000..c84b721 --- /dev/null +++ b/SockAddress.h @@ -0,0 +1,202 @@ +#pragma once + +/* + * Copyright (C) 2019 by Thomas Early N7TAE + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +class CSockAddress +{ +public: + CSockAddress() + { + Clear(); + } + + CSockAddress(const struct sockaddr_storage &from) + { + Clear(); + if (AF_INET == from.ss_family) + memcpy(&addr, &from, sizeof(struct sockaddr_in)); + else + memcpy(&addr, &from, sizeof(struct sockaddr_in6)); + } + + CSockAddress(const int family, const unsigned short port, const char *address) + { + Clear(); + if (AF_INET==family && address) { + struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr; + addr4->sin_family = AF_INET; + addr4->sin_port = htons(port); + if (0 == strncasecmp(address, "loc", 3)) + inet_pton(AF_INET, "127.0.0.1", &(addr4->sin_addr)); + else if (0 == strncasecmp(address, "any", 3)) + inet_pton(AF_INET, "0.0.0.0", &(addr4->sin_addr)); + else + inet_pton(AF_INET, address, &(addr4->sin_addr)); + } else if (AF_INET6==family && address) { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr; + addr6->sin6_family = AF_INET6; + addr6->sin6_port = htons(port); + if (0 == strncasecmp(address, "loc", 3)) + inet_pton(AF_INET6, "::1", &(addr6->sin6_addr)); + else if (0 == strncasecmp(address, "any", 3)) + inet_pton(AF_INET6, "::", &(addr6->sin6_addr)); + else + inet_pton(AF_INET6, address, &(addr6->sin6_addr)); + } else if (AF_UNSPEC == family) { + memset(&addr, 0, sizeof(struct sockaddr_storage)); + } + } + + ~CSockAddress() {} + + void Initialize(int family, uint16_t port = 0U, const char *address = NULL) + { + Clear(); + addr.ss_family = (sa_family_t)family; + if (AF_INET == family) { + struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr; + addr4->sin_port = htons(port); + if (address) { + if (0 == strncasecmp(address, "loc", 3)) + inet_pton(AF_INET, "127.0.0.1", &(addr4->sin_addr)); + else if (0 == strncasecmp(address, "any", 3)) + inet_pton(AF_INET, "0.0.0.0", &(addr4->sin_addr)); + else + inet_pton(AF_INET, address, &(addr4->sin_addr)); + } + } else { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr; + addr6->sin6_port = htons(port); + if (address) { + if (0 == strncasecmp(address, "loc", 3)) + inet_pton(AF_INET6, "::1", &(addr6->sin6_addr)); + else if (0 == strncasecmp(address, "any", 3)) + inet_pton(AF_INET6, "::", &(addr6->sin6_addr)); + else + inet_pton(AF_INET6, address, &(addr6->sin6_addr)); + } + } + } + + CSockAddress &operator=(CSockAddress &from) + { + Clear(); + if (AF_INET == from.addr.ss_family) + memcpy(&addr, &from, sizeof(struct sockaddr_in)); + else + memcpy(&addr, &from, sizeof(struct sockaddr_in6)); + return *this; + } + + bool operator==(CSockAddress &from) + { + if (addr.ss_family == from.addr.ss_family) { + if (AF_INET == addr.ss_family) { + return (0==memcmp(&addr, &from, sizeof(struct sockaddr_in))); + } else { + return (0==memcmp(&addr, &from, sizeof(struct sockaddr_in6))); + } + } else + return false; + } + + bool AddressIsZero() + { + if (AF_INET == addr.ss_family) { + struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr; + return (addr4->sin_addr.s_addr == 0U); + } else { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr; + for (unsigned int i=0; i<16; i++) { + if (addr6->sin6_addr.s6_addr[i]) + return false; + } + return true; + } + } + + void ClearAddress() + { + if (AF_INET == addr.ss_family) { + struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr; + addr4->sin_addr.s_addr = 0U; + } else { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr; + memset(&(addr6->sin6_addr.s6_addr), 0, 16); + } + } + + const char *GetAddress() + { + if (AF_INET == addr.ss_family) { + struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr; + if (NULL == inet_ntop(AF_INET, &(addr4->sin_addr), straddr, INET6_ADDRSTRLEN)) + return "ERROR"; + } else { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr; + if (NULL == inet_ntop(AF_INET6, &(addr6->sin6_addr), straddr, INET6_ADDRSTRLEN)) + return "ERROR"; + } + + return straddr; + } + + int GetFamily() + { + return addr.ss_family; + } + + unsigned short GetPort() + { + if (AF_INET == addr.ss_family) { + struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr; + return ntohs(addr4->sin_port); + } else { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr; + return ntohs(addr6->sin6_port); + } + } + + struct sockaddr *GetPointer() + { + return (struct sockaddr *)&addr; + } + + size_t GetSize() + { + if (AF_INET == addr.ss_family) + return sizeof(struct sockaddr_in); + else + return sizeof(struct sockaddr_in6); + } + + void Clear() + { + memset(&addr, 0, sizeof(struct sockaddr_storage)); + } + +private: + struct sockaddr_storage addr; + char straddr[INET6_ADDRSTRLEN]; +}; diff --git a/TCPReaderWriterClient.cpp b/TCPReaderWriterClient.cpp new file mode 100644 index 0000000..227dbdf --- /dev/null +++ b/TCPReaderWriterClient.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2010-2013 by Jonathan Naylor G4KLX + * Copyright (C) 2019 by Thomas A. Early N7TAE + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "TCPReaderWriterClient.h" +#include +#include +#include +#include + + +CTCPReaderWriterClient::CTCPReaderWriterClient(const std::string &address, int family, const std::string &port) : +m_address(address), +m_family(family), +m_port(port), +m_fd(-1) +{ +} + +CTCPReaderWriterClient::CTCPReaderWriterClient() : m_fd(-1) +{ +} + +CTCPReaderWriterClient::~CTCPReaderWriterClient() +{ +} + +bool CTCPReaderWriterClient::Open(const std::string &address, int family, const std::string &port) +{ + m_address = address; + m_family = family; + m_port = port; + + return Open(); +} + +bool CTCPReaderWriterClient::Open() +{ + if (m_fd != -1) { + fprintf(stderr, "ERROR: port for '%s' is already open!\n", m_address.c_str()); + return true; + } + + if (0 == m_address.size() || 0 == m_port.size() || 0 == std::stoul(m_port)) { + fprintf(stderr, "ERROR: '[%s]:%s' is malformed!\n", m_address.c_str(), m_port.c_str()); + return true; + } + + if (AF_INET!=m_family && AF_INET6!=m_family && AF_UNSPEC!=m_family) { + fprintf(stderr, "ERROR: family must be AF_INET, AF_INET6 or AF_UNSPEC\n"); + return true; + } + + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + //hints.ai_flags = AI_PASSIVE; + hints.ai_protocol = IPPROTO_TCP; + + struct addrinfo *res; + int s = EAI_AGAIN; + int count = 0; + while (EAI_AGAIN==s and count++<20) { + // connecting to a server, so we can wait until it's ready + s = getaddrinfo(m_address.c_str(), m_port.c_str(), &hints, &res); + if (s && s != EAI_AGAIN) { + fprintf(stderr, "ERROR: getaddrinfo of %s: %s\n", m_address.c_str(), gai_strerror(s)); + return true; + } + std::this_thread::sleep_for(std::chrono::seconds(3)); + } + + if (EAI_AGAIN == s) { + fprintf(stderr, "ERROR getaddrinfo of %s failed 20 times\n", m_address.c_str()); + return true; + } + + struct addrinfo *rp; + for (rp = res; rp != NULL; rp = rp->ai_next) { + m_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (m_fd == -1) + continue; + + if (connect(m_fd, rp->ai_addr, rp->ai_addrlen)) { + Close(); + continue; + } else { + char buf[INET6_ADDRSTRLEN]; + void *addr; + if (AF_INET == rp->ai_family) { + struct sockaddr_in *addr4 = (struct sockaddr_in *)rp->ai_addr; + addr = &(addr4->sin_addr); + } else { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)rp->ai_addr; + addr = &(addr6->sin6_addr); + } + if (inet_ntop(rp->ai_family, addr, buf, INET6_ADDRSTRLEN)) + fprintf(stderr, "Successfully connected to %s at [%s]:%s\n", m_address.c_str(), buf, m_port.c_str()); + break; + } + } + freeaddrinfo(res); + + if (rp == NULL) { + fprintf(stderr, "Could not connect to any system returned by %s\n", m_address.c_str()); + m_fd = -1; + return true; + } + + return false; +} + +int CTCPReaderWriterClient::ReadExact(unsigned char *buf, const unsigned int length) +{ + unsigned int offset = 0U; + + do { + int n = Read(buf + offset, length - offset); + if (n < 0) + return n; + + offset += n; + } while ((length - offset) > 0U); + + return length; +} + +int CTCPReaderWriterClient::Read(unsigned char* buffer, const unsigned int length) +{ + assert(buffer != NULL); + assert(length > 0U); + assert(m_fd != -1); + + ssize_t len = recv(m_fd, buffer, length, 0); + if (len <= 0) { + if (len < 0) + fprintf(stderr, "Error returned from recv, err=%d\n", errno); + return -1; + } + + return len; +} + +int CTCPReaderWriterClient::ReadLine(std::string& line) +{ + unsigned char c; + int resultCode; + int len = 0; + line = ""; + + do + { + resultCode = Read(&c, 1); + if(resultCode == 1) { + line += c; + len++; + } + } while(c != '\n' && resultCode == 1); + + return resultCode <= 0 ? resultCode : len; +} + +bool CTCPReaderWriterClient::Write(const unsigned char *buffer, const unsigned int length) +{ + assert(buffer != NULL); + assert(length > 0U); + assert(m_fd != -1); + + ssize_t ret = send(m_fd, (char *)buffer, length, 0); + if (ret != ssize_t(length)) { + if (ret < 0) + fprintf(stderr, "Error returned from send, err=%d\n", errno); + else + fprintf(stderr, "Error only wrote %d of %d bytes\n", int(ret), int(length)); + return true; + } + + return false; +} + +bool CTCPReaderWriterClient::WriteLine(const std::string& line) +{ + std::string lineCopy(line); + if(lineCopy.size() > 0 && lineCopy.at(lineCopy.size() - 1) != '\n') + lineCopy.append("\n"); + + size_t len = lineCopy.size(); + bool result = true; + for(size_t i = 0; i < len && result; i++){ + unsigned char c = lineCopy.at(i); + result = Write(&c , 1); + } + + return result; +} + +void CTCPReaderWriterClient::Close() +{ + if (m_fd != -1) { + close(m_fd); + m_fd = -1; + } +} diff --git a/TCPReaderWriterClient.h b/TCPReaderWriterClient.h new file mode 100644 index 0000000..0aed516 --- /dev/null +++ b/TCPReaderWriterClient.h @@ -0,0 +1,59 @@ +#pragma once + +/* end of inma once */ +/* + * Copyright (C) 2010,2011,2012,2013 by Jonathan Naylor G4KLX + * Copyright (C) 2019 by Thomas A. Early N7TAE + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class CTCPReaderWriterClient { +public: + CTCPReaderWriterClient(const std::string &address, int family, const std::string &port); + CTCPReaderWriterClient(); + ~CTCPReaderWriterClient(); + + bool Open(const std::string &address, int family, const std::string &port); + bool Open(); + + int ReadExact(unsigned char *buffer, const unsigned int length); + int Read(unsigned char *buffer, const unsigned int length); + int ReadLine(std::string &line); + bool Write(const unsigned char* buffer, const unsigned int length); + bool WriteLine(const std::string &line); + int GetFD() { return m_fd; } + + void Close(); + +private: + std::string m_address; + int m_family; + std::string m_port; + int m_fd; +}; diff --git a/Timer.h b/Timer.h new file mode 100644 index 0000000..218c76a --- /dev/null +++ b/Timer.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2019 by Thomas A. Early N7TAE + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +#include +#include + +class CTimer +{ +public: + CTimer() { start(); } + ~CTimer() {} + void start() { + starttime = std::chrono::steady_clock::now(); + } + double time() { + std::chrono::steady_clock::duration elapsed = std::chrono::steady_clock::now() - starttime; + return double(elapsed.count()) * std::chrono::steady_clock::period::num / std::chrono::steady_clock::period::den; + } +private: + std::chrono::steady_clock::time_point starttime; +}; diff --git a/UnixDgramSocket.cpp b/UnixDgramSocket.cpp new file mode 100644 index 0000000..e494619 --- /dev/null +++ b/UnixDgramSocket.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2019 by Thomas Early N7TAE + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "UnixDgramSocket.h" + +CUnixDgramReader::CUnixDgramReader() : fd(-1) {} + +CUnixDgramReader::~CUnixDgramReader() +{ + Close(); +} + +bool CUnixDgramReader::Open(const char *path) // returns true on failure +{ + fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (fd < 0) { + fprintf(stderr, "CUnixDgramReader::Open: socket() failed: %s\n", strerror(errno)); + return true; + } + //fcntl(fd, F_SETFL, O_NONBLOCK); + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path+1, path, sizeof(addr.sun_path)-2); + + int rval = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (rval < 0) { + fprintf(stderr, "CUnixDgramReader::Open: bind() failed: %s\n", strerror(errno)); + close(fd); + fd = -1; + return true; + } + return false; +} + +ssize_t CUnixDgramReader::Read(void *buf, size_t size) +{ + if (fd < 0) + return -1; + ssize_t len = read(fd, buf, size); + if (len < 1) + fprintf(stderr, "CUnixDgramReader::Read read() returned %d: %s\n", int(len), strerror(errno)); + return len; +} + +void CUnixDgramReader::Close() +{ + if (fd >= 0) + close(fd); + fd = -1; +} + +int CUnixDgramReader::GetFD() +{ + return fd; +} + +CUnixDgramWriter::CUnixDgramWriter() {} + +CUnixDgramWriter::~CUnixDgramWriter() {} + +void CUnixDgramWriter::SetUp(const char *path) // returns true on failure +{ + // setup the socket address + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path+1, path, sizeof(addr.sun_path)-2); +} + +ssize_t CUnixDgramWriter::Write(const void *buf, size_t size) +{ + // open the socket + int fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (fd < 0) { + fprintf(stderr, "Failed to open socket %s : %s\n", addr.sun_path+1, strerror(errno)); + return -1; + } + // connect to the receiver + int rval = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (rval < 0) { + fprintf(stderr, "Failed to connect to socket %s : %s\n", addr.sun_path+1, strerror(errno)); + close(fd); + return -1; + } + + ssize_t written = 0; + int count = 0; + while (written <= 0) { + written = write(fd, buf, size); + if (written == (ssize_t)size) + break; + else if (written < 0) + fprintf(stderr, "ERROR: faied to write to %s : %s\n", addr.sun_path+1, strerror(errno)); + else if (written == 0) + fprintf(stderr, "Warning: zero bytes written to %s\n", addr.sun_path+1); + else if (written != (ssize_t)size) { + fprintf(stderr, "ERROR: only %d of %d bytes written to %s\n", (int)written, (int)size, addr.sun_path+1); + break; + } + if (++count >= 100) { + fprintf(stderr, "ERROR: Write failed after %d attempts\n", count-1); + break; + } + std::this_thread::sleep_for(std::chrono::microseconds(5)); + } + + close(fd); + return written; +} diff --git a/UnixDgramSocket.h b/UnixDgramSocket.h new file mode 100644 index 0000000..fe193e9 --- /dev/null +++ b/UnixDgramSocket.h @@ -0,0 +1,45 @@ +#pragma once +/* + * Copyright (C) 2019 by Thomas Early N7TAE + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include + +class CUnixDgramReader +{ +public: + CUnixDgramReader(); + ~CUnixDgramReader(); + bool Open(const char *path); + ssize_t Read(void *buf, size_t size); + void Close(); + int GetFD(); +private: + int fd; +}; + +class CUnixDgramWriter +{ +public: + CUnixDgramWriter(); + ~CUnixDgramWriter(); + void SetUp(const char *path); + ssize_t Write(const void *buf, size_t size); +private: + struct sockaddr_un addr; +}; diff --git a/announce/already_linked.dat b/announce/already_linked.dat index ba1910a..432de59 100644 Binary files a/announce/already_linked.dat and b/announce/already_linked.dat differ diff --git a/announce/already_unlinked.dat b/announce/already_unlinked.dat index 88c7035..bd4f529 100644 Binary files a/announce/already_unlinked.dat and b/announce/already_unlinked.dat differ diff --git a/announce/baddtmfcmd.dat b/announce/baddtmfcmd.dat new file mode 100644 index 0000000..b6190d1 Binary files /dev/null and b/announce/baddtmfcmd.dat differ diff --git a/announce/connected2network.dat b/announce/connected2network.dat new file mode 100644 index 0000000..dd9e4da Binary files /dev/null and b/announce/connected2network.dat differ diff --git a/announce/failed_link.dat b/announce/failed_link.dat new file mode 100644 index 0000000..4704abb Binary files /dev/null and b/announce/failed_link.dat differ diff --git a/announce/failed_linked.dat b/announce/failed_linked.dat deleted file mode 100644 index 878ec34..0000000 Binary files a/announce/failed_linked.dat and /dev/null differ diff --git a/announce/gatewaynotfound.dat b/announce/gatewaynotfound.dat new file mode 100644 index 0000000..2c1de69 Binary files /dev/null and b/announce/gatewaynotfound.dat differ diff --git a/announce/gatewayrestart.dat b/announce/gatewayrestart.dat new file mode 100644 index 0000000..f21557f Binary files /dev/null and b/announce/gatewayrestart.dat differ diff --git a/announce/id.dat b/announce/id.dat index b2151dc..43b37f1 100644 Binary files a/announce/id.dat and b/announce/id.dat differ diff --git a/announce/index.dat b/announce/index.dat new file mode 100644 index 0000000..30996eb --- /dev/null +++ b/announce/index.dat @@ -0,0 +1,63 @@ +A 0 29 +B 30 32 +C 63 34 +D 98 32 +E 131 26 +F 158 31 +G 190 36 +H 227 31 +I 259 28 +J 288 36 +K 325 28 +L 354 28 +M 383 34 +N 418 32 +O 451 29 +P 481 32 +Q 514 34 +R 549 29 +S 579 33 +T 613 28 +U 642 24 +V 667 44 +W 712 40 +X 753 33 +Y 787 31 +Z 819 36 +alpha 856 38 +bravo 895 38 +charlie 934 37 +delta 972 37 +echo 1010 33 +foxtrot 1044 56 +golf 1101 38 +hotel 1140 39 +india 1180 36 +juliette 1217 39 +kilo 1257 33 +lima 1291 41 +mike 1333 33 +november 1367 38 +oscar 1406 40 +papa 1447 35 +quebec 1483 36 +romeo 1520 39 +sierra 1560 35 +tango 1596 40 +uniform 1637 45 +victor 1683 34 +whiskey 1718 33 +X-ray 1752 40 +yankee 1793 39 +zulu 1833 38 +1 1872 34 +2 1907 28 +3 1936 37 +4 1974 35 +5 2010 37 +6 2048 35 +7 2084 38 +8 2123 28 +9 2152 37 +0 2190 33 + diff --git a/announce/linked.dat b/announce/linked.dat index 21047f8..8574279 100644 Binary files a/announce/linked.dat and b/announce/linked.dat differ diff --git a/announce/notincache.dat b/announce/notincache.dat new file mode 100644 index 0000000..bd08f48 Binary files /dev/null and b/announce/notincache.dat differ diff --git a/announce/rebooting.dat b/announce/rebooting.dat new file mode 100644 index 0000000..8418ddb Binary files /dev/null and b/announce/rebooting.dat differ diff --git a/announce/shutdown.dat b/announce/shutdown.dat new file mode 100644 index 0000000..9a762c7 Binary files /dev/null and b/announce/shutdown.dat differ diff --git a/announce/speak.dat b/announce/speak.dat new file mode 100644 index 0000000..046de16 Binary files /dev/null and b/announce/speak.dat differ diff --git a/announce/unlinked.dat b/announce/unlinked.dat index b2151dc..0686ad2 100644 Binary files a/announce/unlinked.dat and b/announce/unlinked.dat differ diff --git a/aprs.cpp b/aprs.cpp index 4390add..21c97f7 100644 --- a/aprs.cpp +++ b/aprs.cpp @@ -49,10 +49,8 @@ void CAPRS::SelectBand(short int rptr_idx, unsigned short streamID) // Parameter buf is either: // 12 bytes(packet from repeater was 29 bytes) or // 15 bytes(packet from repeater was 32 bytes) -// Parameter len is either 12 or 15, because we took passed over the first 17 bytes -// in the repeater data // Paramter seq is the byte at pos# 16(counting from zero) in the repeater data -void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned char *buf, unsigned int len) +void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned char *buf) { unsigned char aprs_data[200]; char aprs_buf[1024]; @@ -60,8 +58,6 @@ void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned cha short int rptr_idx = -1; - len = len; - for (short int i = 0; i < 3; i++) { if (streamID == aprs_streamID[i].streamID) { rptr_idx = i; @@ -70,7 +66,7 @@ void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned cha } if ((rptr_idx < 0) || (rptr_idx > 2)) { - // printf("ERROR in aprs_process_text: rptr_idx %d is invalid\n", rptr_idx); + printf("ERROR in aprs_process_text: rptr_idx %d is invalid\n", rptr_idx); return; } @@ -93,7 +89,7 @@ void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned cha if ((tnow - aprs_streamID[rptr_idx].last_time) < 30) return; - if (aprs_sock == -1) + if (aprs_sock.GetFD() == -1) return; char *p = strchr((char*)aprs_data, ':'); @@ -114,7 +110,7 @@ void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned cha sprintf(aprs_buf, "%s,qAR,%s:%s\r\n", hdr, m_rptr->mod[rptr_idx].call.c_str(), aud); // printf("GPS-A=%s", aprs_buf); - int rc = WriteSock(aprs_buf, strlen(aprs_buf)); + int rc = aprs_sock.Write((unsigned char *)aprs_buf, strlen(aprs_buf)); if (rc == -1) { if ((errno == EPIPE) || (errno == ECONNRESET) || @@ -127,9 +123,8 @@ void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned cha (errno == ENETUNREACH) || (errno == EHOSTDOWN) || (errno == ENOTCONN)) { - printf("CAPRS::ProcessText(): APRS_HOST closed connection,error=%d\n",errno); - close(aprs_sock); - aprs_sock = -1; + printf("CAPRS::ProcessText(): APRS_HOST closed connection, error=%d\n",errno); + aprs_sock.Close(); } else /* if it is WOULDBLOCK, we will not go into a loop here */ printf("CAPRS::ProcessText(): send error=%d\n", errno); } @@ -156,23 +151,9 @@ void CAPRS::Init() aprs_streamID[i].last_time = 0; } - /* Initialize the APRS host */ - memset(&aprs_addr,0,sizeof(struct sockaddr_in)); - aprs_addr_len = sizeof(aprs_addr); - return; } -int CAPRS::GetSock() -{ - return aprs_sock; -} - -void CAPRS::SetSock(int value) -{ - aprs_sock = value; -} - bool CAPRS::WriteData(short int rptr_idx, unsigned char *data) { @@ -256,90 +237,12 @@ unsigned int CAPRS::GetData(short int rptr_idx, unsigned char *data, unsigned in void CAPRS::Open(const std::string OWNER) { - fd_set fdset; - struct timeval tv; - short int MAX_WAIT = 15; /* 15 seconds wait time MAX */ - int val = 1; - socklen_t val_len; char snd_buf[512]; char rcv_buf[512]; - - bool ok = ResolveRmt(m_rptr->aprs.ip.c_str(), SOCK_STREAM, &aprs_addr); - if (!ok) { - printf("Can't resolve APRS_HOST %s\n", m_rptr->aprs.ip.c_str()); - return; - } - - /* fill it in */ - aprs_addr.sin_family = AF_INET; - aprs_addr.sin_port = htons(m_rptr->aprs.port); - - aprs_addr_len = sizeof(aprs_addr); - - aprs_sock = socket(PF_INET, SOCK_STREAM, 0); - if (aprs_sock == -1) { - printf("Failed to create aprs socket,error=%d\n",errno); - return; - } - fcntl(aprs_sock,F_SETFL,O_NONBLOCK); - - val = 1; - if (setsockopt(aprs_sock,IPPROTO_TCP,TCP_NODELAY,(char *)&val, sizeof(val)) == -1) { - printf("setsockopt TCP_NODELAY TCP for aprs socket failed,error=%d\n",errno); - close(aprs_sock); - aprs_sock = -1; - return; - } - - printf("Trying to connect to APRS...\n"); - int rc = connect(aprs_sock, (struct sockaddr *)&aprs_addr, aprs_addr_len); - if (rc != 0) { - if (errno == EINPROGRESS) { - printf("Waiting for up to %d seconds for APRS_HOST\n", MAX_WAIT); - while (MAX_WAIT > 0) { - tv.tv_sec = 0; - tv.tv_usec = 0; - FD_ZERO(&fdset); - FD_SET(aprs_sock, &fdset); - rc = select(aprs_sock + 1, NULL, &fdset, NULL, &tv); - - if (rc < 0) { - printf("Failed to connect to APRS...select,error=%d\n", errno); - close(aprs_sock); - aprs_sock = -1; - return; - } else if (rc == 0) { /* timeout */ - MAX_WAIT--; - sleep(1); - } else { - val = 1; /* Assume it fails */ - val_len = sizeof(val); - if (getsockopt(aprs_sock, SOL_SOCKET, SO_ERROR, (char *) &val, &val_len) < 0) { - printf("Failed to connect to APRS...getsockopt, error=%d\n", errno); - close(aprs_sock); - aprs_sock = -1; - return; - } else if (val == 0) - break; - - MAX_WAIT--; - sleep(1); - } - } - if (MAX_WAIT == 0) { - printf("Failed to connect to APRS...timeout\n"); - close(aprs_sock); - aprs_sock = -1; - return; - } - } else { - printf("Failed to connect to APRS, error=%d\n", errno); - close(aprs_sock); - aprs_sock = -1; - return; - } - } - printf("Connected to APRS %s:%d\n", m_rptr->aprs.ip.c_str(), m_rptr->aprs.port); + while (aprs_sock.Open(m_rptr->aprs.ip, AF_UNSPEC, std::to_string(m_rptr->aprs.port))) { + fprintf(stderr, "Failed to open %s, retry in 10 seconds...\n", m_rptr->aprs.ip.c_str()); + std::this_thread::sleep_for(std::chrono::seconds(10)); + } /* login to aprs */ sprintf(snd_buf, "user %s pass %d vers qngateway 2.99 UDP 5 ", OWNER.c_str(), m_rptr->aprs_hash); @@ -353,10 +256,10 @@ void CAPRS::Open(const std::string OWNER) strcat(snd_buf, "\r\n"); while (true) { - rc = WriteSock(snd_buf, strlen(snd_buf)); + int rc = aprs_sock.Write((unsigned char *)snd_buf, strlen(snd_buf)); if (rc < 0) { if (errno == EWOULDBLOCK) { - recv(aprs_sock, rcv_buf, sizeof(rcv_buf), 0); + aprs_sock.Read((unsigned char *)rcv_buf, sizeof(rcv_buf)); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } else { printf("APRS login command failed, error=%d\n", errno); @@ -367,7 +270,7 @@ void CAPRS::Open(const std::string OWNER) break; } } - recv(aprs_sock, rcv_buf, sizeof(rcv_buf), 0); + aprs_sock.Read((unsigned char *)rcv_buf, sizeof(rcv_buf)); return; } @@ -495,55 +398,6 @@ unsigned int CAPRS::CalcCRC(unsigned char* buf, unsigned int len) return (~my_crc & 0xffff); } -ssize_t CAPRS::WriteSock(char *buffer, size_t n) -{ - ssize_t num_written = 0; - size_t tot_written = 0; - char *buf = buffer; - - for (tot_written = 0; tot_written < n;) { - num_written = write(aprs_sock, buf, n - tot_written); - if (num_written <= 0) { - if ((num_written == -1) && (errno == EINTR)) - continue; - else - return num_written; - } - tot_written += num_written; - buf += num_written; - } - return tot_written; -} - -bool CAPRS::ResolveRmt(const char *name, int type, struct sockaddr_in *addr) -{ - struct addrinfo hints; - struct addrinfo *res; - struct addrinfo *rp; - bool found = false; - - memset(&hints, 0x00, sizeof(struct addrinfo)); - hints.ai_family = AF_INET; - hints.ai_socktype = type; - - int rc = getaddrinfo(name, NULL, &hints, &res); - if (rc != 0) { - printf("getaddrinfo return error code %d for [%s]\n", rc, name); - return false; - } - - for (rp = res; rp != NULL; rp = rp->ai_next) { - if ((rp->ai_family == AF_INET) && - (rp->ai_socktype == type)) { - memcpy(addr, rp->ai_addr, sizeof(struct sockaddr_in)); - found = true; - break; - } - } - freeaddrinfo(res); - return found; -} - CAPRS::CAPRS(SRPTR *prptr) { m_rptr = prptr; @@ -551,4 +405,10 @@ CAPRS::CAPRS(SRPTR *prptr) CAPRS::~CAPRS() { + aprs_sock.Close(); +} + +void CAPRS::CloseSock() +{ + aprs_sock.Close(); } diff --git a/aprs.h b/aprs.h index ac97c2d..29ce301 100644 --- a/aprs.h +++ b/aprs.h @@ -23,6 +23,7 @@ #include #include +#include "TCPReaderWriterClient.h" #include "IRCutils.h" enum aprs_level { al_none, al_$1, al_$2, al_c1, al_r1, al_c2, al_csum1, al_csum2, al_csum3, al_csum4, al_data, al_end }; @@ -47,7 +48,6 @@ typedef struct rptr_tag{ std::string band; /* 23cm ... */ double frequency, offset, latitude, longitude, range, agl; std::string desc1, desc2, desc, url, package_version; - SPORTIP portip; } mod[3]; } SRPTR; @@ -58,19 +58,14 @@ public: ~CAPRS(); SRPTR *m_rptr; void SelectBand(short int rptr_idx, unsigned short streamID); - void ProcessText(unsigned short streamID, unsigned char seq, unsigned char *buf, unsigned int len); - ssize_t WriteSock(char *buffer, size_t n); + void ProcessText(unsigned short streamID, unsigned char seq, unsigned char *buf); void Open(const std::string OWNER); void Init(); - int GetSock(); - void SetSock(int value); + void CloseSock(); + CTCPReaderWriterClient aprs_sock; private: // data - // the aprs TCP socket - int aprs_sock = -1; - struct sockaddr_in aprs_addr; - socklen_t aprs_addr_len; struct { aprs_level al; unsigned char data[300]; @@ -93,5 +88,4 @@ private: bool AddData(short int rptr_idx, unsigned char *data); bool CheckData(short int rptr_idx); unsigned int CalcCRC(unsigned char* buf, unsigned int len); - bool ResolveRmt(const char *name, int type, struct sockaddr_in *addr); }; diff --git a/bash_aliases b/bash_aliases new file mode 100644 index 0000000..3630587 --- /dev/null +++ b/bash_aliases @@ -0,0 +1,36 @@ +# Copyright (c) 2019 by Thomas A. Early N7TAE + +# copy this to ~/.bash_aliases if you don't want to use qnadmin to start and stop QnetGateway + +function start () { + if [ $# == 1 ]; then + sudo make installbase && sudo make install${1} && sudo journalctl -u qn${1} -f + elif [ $# == 2 ]; then + sudo make installbase && sudo make install${1} && sudo journalctl -u qn${2} -f + else + echo "Usage: start module_name [watch_module]" + echo "Installs the base system and the module_name prefixed with 'qn' and tails the log." + echo "Use watch_module if you want to tail a different log" + echo "Only use this alias for systems with a single defined module." + echo "You must be in the QnetGateway build directory" + fi +} + +function stop () { + if [ $# == 1 ]; then + sudo make uninstallbase && sudo make uninstall${1} + else + echo "usage: stop module_name" + echo "Uninstalls the base system and the module_name prefixed with 'qn'." + echo "Use this alias on for systems with a single defined module." + fi +} + +function watch () { + if [ $# == 1 ]; then + sudo journalctl -u qn${1} -f + else + echo "usage: watch service_name" + echo "Tails the log from the service_name prefixed with 'qn'." + fi +} diff --git a/defaults b/defaults new file mode 100644 index 0000000..701f90d --- /dev/null +++ b/defaults @@ -0,0 +1,211 @@ +# +# Copyright (c) 2019 by Thomas A. Early N7TAE +# +# 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 . + +######################################################################################################################### +# # +# QnetGateway Default Parameter Values # +# # +######################################################################################################################### +# What follows need to also be valid bash shell variable definitions, therefore: +# No white space on either side of the equal sign (=) +# String values should be quoted if they contain any special chars, including white space +# If a string value is a simple word, it doesn't need to be quoted +# Use the single quote (') for quoting strings, not the double quote(") +# Comments can come after a key=value definition, introduced by a pound-sign (#) +# +# if a definition is commented out, it means that key has no default value. And it is +# include here just as a reference. + +########################################################################################################################## +# +# IRCDDB - You MUST use a legal callsign for logging into any IRC network +# +#ircddb_login_d='' # login callsign for the ircDDB network +ircddb0_host_d='rr.openquad.net' # other irc networks include group1-irc.ircddb.net +ircddb0_port_d=9007 # not a good idea to change! +ircddb0_password_d='' # not needed for rr.openquad.net +ircddb1_host_d='' # second irc network +ircddb1_port_d=9007 # not a good idea to change! +ircddb1_password_d='' # not needed for rr.openquad.net + +########################################################################################################################## +# +# GATEWAY +# +gateway_header_regen_d=true # regenerate headers from incoming data +gateway_send_qrgs_maps_d=true # send frequency, offset, coordinates and url to irc-server +gateway_ip_d='ANY_PORT' # the g2 port +gateway_port_d=40000 # don't change +gateway_ipv6_ip_d='ANY_PORT' +gateway_ipv6_port_d=9011 # IANA-approved DStar rouing port +gateway_gate2link_d='gate2link' # Unix sockets between qngateway and QnetLink +gateway_link2gate_d='link2gate' # all Unix sockets are on the file system, but hidden from view +gateway_modem2gate_d='modem2gate' # Unix Sockets between the modem(s) and the gateway (1 in, 3 out) +gateway_gate2modema_d='gate2modema' +gateway_gate2modemb_d='gate2modemb' +gateway_gate2modemc_d='gate2modemc' +gateway_latitude_d=0 # you can leave this unspecified for a mobile rig +gateway_longitude_d=0 # like the latitude +gateway_desc1_d='' # maximum of 20 characters, most special symbols are not allowed +gateway_desc2_d='' # just like desc1 +gateway_url_d='github.com/n7tae/QnetGateway' # 80 characters max +gateway_find_route_d='' # CSV list of route(s) to load on boot-up (prevents the "not in cache" message) + +########################################################################################################################## +# +# APRS - for tracking users and also this repeater. +# +aprs_enable_d=true # send info to APRS +aprs_host_d='rotate.aprs2.net' # the APRS network server +aprs_port_d=14580 # and port +aprs_interval_d=40 # keep-alive in minutes +aprs_filter_d='' # advanced feature + +########################################################################################################################## +# +# LINK - controls the behavior of QnetLink (qnlink) +# +#link_admin_d='' # these comma-separated list of users can execute scripts, block dongles, reload the gwys.txt +#link_link_unlink_d='' # if defined, comma-separated list of users that can link and unlink a repeater +#link_no_link_unlink_d='' # if defined, comma-separated list of users that cannot link or unlink, it's a blacklist + # if the blacklist is defined (even if it's empty), the link_unlink will not be read +link_incoming_ip_d='0.0.0.0' # incoming ip address of qnlink, '0.0.0.0' means accepts any connection. +link_ref_port_d=20001 # port for REF linking, don't change +link_xrf_port_d=30001 # port for XRF linking, don't change +link_dcs_port_d=30051 # port for DCS linking, don't change +link_announce_d=true # do link, unlink, etc. announcements +link_acknowledge_d=true # send text acknowledgment on key-up +link_max_dongles_d=5 # maximum number of linked hot-spots + +########################################################################################################################## +# +# GENERIC MODULE - These will be defined for any and all defined modules +# +module_x_link_at_start='' # For example, set to 'REF001 C' to link module to 1-charlie when the module starts. +module_x_auto_link=true # attempt to automatically re-link if the link should time out. +module_x_inactivity=0 # if no activity for this many minutes unlink reflector. Zero means no timer. +module_x_callsign='' # if you operate in a 'restriction mode', use your personal callsign. Usually leave this empty. +module_x_packet_wait=25 # how many milliseconds to wait on packets in a voicestream +module_x_acknowledge=false # Do you want an ACK back? +module_x_ack_delay=250 # millisecond delay before acknowledgment +module_x_range=0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles +module_x_agl=0 # the height above ground level for this repeater's antenna + +########################################################################################################################## +# +# MMDVMHost - Special parameters when: module_x='mmdvmhost' +# +mmdvmhost_tx_frequency=0 # in MHz, not required, set in MMDVM.qn in Hz +mmdvmhost_rx_frequency=0 # in Mhz, not required, set in MMDVM.qn in Hz +mmdvmhost_internal_ip='0.0.0.0' # where MMDVMHost will find the QnetRelay program +mmdvmhost_gateway_port=20010 # which port will QnetRelay be sending on +mmdvmhost_local_port=20011 # which port will MMDVMHost be sending on + +########################################################################################################################## +# +# MMDVM Modem - Special parameters when: module_x='mmdmvmmodem' +# +mmdvmmodem_device='/dev/ttyAMA0' # where QnetModem will find the MMDVM modem +mmdvmmodem_tx_frequency=0 # in MHz, you MUST set a valid transmitter frequency! +mmdvmmodem_rx_frequency=0 # in MHz. If unset, then it's simplex, where rx=tx +mmdvmmodem_tx_offset=0 # in MHz. A frequency tweak. +mmdvmmodem_rx_offset=0 # in MHz. A frequency tweak. +mmdvmmodem_duplex=false # set to true for duplex for modems that support it +mmdvmmodem_rx_invert=false # receiver gate +mmdvmmodem_tx_invert=true # transmitter gate +mmdvmmodem_ptt_invert=false # push-to-talk gate +mmdvmmodem_tx_delay=100 # delay in milliseconds +mmdvmmodem_rx_level=128 # range is 0-255 +mmdvmmodem_tx_level=128 # range is 0-255 + +########################################################################################################################## +# +# ITAP - Special parameters when: module_x='itap' +# +itap_device='/dev/ttyUSB0' # where the serial-to-USB cable show up +itap_frequency=0 # in MHz, not required for either mode, for AP mode, the simplex frequency is set on your radio + +########################################################################################################################## +# +# DVAP - Special parameters when: module.x='dvap' +# +module_x_frequency=0 # in MHz, you must specify an operational frequency for the DVAP +module_x_offset=0 # it's a frequency tweak, in Hz +dvap_power=10 # TX power level: -12 to 10, 10 is maximum power +dvap_squelch=-100 # RX Squelch: -128 to -45, -100 to -80 usually works best +dvap_serial_number='APXXXXXX' # The serial number of your DVAP is visible through the bottom of the case + +########################################################################################################################## +# +# DVRPTR - Special parameters when: module_x='dvrptr' +# +# if you don't know what your DVRPTR serial number is, look in the log file after running qndvrptr +dvrptr_tx_frequency=0 # in MHz, not required +dvrptr_rx_frequency=0 # in MHz, also not required +dvrptr_serial_number='00.00.00.00' # the DVRPTR serial number +dvrptr_rf_on='RFON' # put this in YRCALL to disable the channel +dvrptr_rf_off='RFOFF' # put this in YRCALL to enable the channel +dvrptr_rx_level=80 # see the DVRPTR V1 manual +dvrptr_duplex=false # set to true if the module is duplex +dvrptr_tx_delay=250 # milliseconds to allow for switching from rx to tx +dvrptr_rqst_count=10 # number of 2-sec intervals before the an unresponsive system is killed +dvrptr_rx_invert=true # if your system isn't hearing you, try false +dvrptr_tx_invert=true # if you're not hearing your system, try false + +########################################################################################################################## +# +# LOGGING - Control extra logging - useful for debugging +# +log_qso_d=false # QSO info goes into the log +log_irc_d=false # IRC debug info +log_dtmf_d=false # DTMF debug info +log_debug_d=false # WARNING, can produce a large number of log entries! + +########################################################################################################################## +# +# DPLUS - Control of dplus (trust system) linking to repeaters and REF reflectors +# +# The following settings do not affect your ability to use dplus linking to XRF or XLX reflectors! +# You must be registered on the DPlus system, see www.dstargateway.org, otherwise authorization will fail, +# even if QnetLink reports a successful authorization. +dplus_authorize_d=false # set to true if you want to use the closed-source DPlus reflectors and/or repeaters +dplus_ref_login_d='' # for logging into REF reflectors, if empty, ircddb_login will be used +dplus_use_reflectors_d=true # set to false if you are not going to link to DPlus reflectors +dplus_use_repeaters_d=true # set to false if you are not going to link to DPlus repeaters +dplus_priority_d=true # set to true if you want DPlus reflector read after gwys.txt + +########################################################################################################################## +# +# FILE - where important QnetGateway files and directories are found. +# +file_status_d='/usr/local/etc/rptr_status' # where repeater status info is passed between services +file_dtmf_d='/tmp' # where DTMF is decoded +file_echotest_d='/var/local' # echo dat files will end up here +file_qnvoice_file_d='/tmp/qnvoice.txt' # where qnvoice will create the play command +file_gwys_d='/usr/local/etc/gwys.txt' # where the list of gateways and reflectors (with ports) is. +file_announce_dir_d='/usr/local/etc' # where the *.dat files are for the verbal link, unlink, etc. announcements + +########################################################################################################################## +# +# TIMINGS - for controlling how to deal with timing issues +# +# most users will not have to override any of these default values +timing_timeout_echo_d=1 # seconds before we assume echo has timed out +timing_timeout_voicemail_d=1 # seconds before we assume voicemail has timed out +timing_timeout_remote_g2_d=2 # after this many seconds with no packets, we assume the tx is closed +timing_timeout_local_rptr_d=1 # local repeater timeout, in seconds +timing_play_wait_d=1 # seconds before echo or voicemail playback occurs, between 1 and 10 +timing_play_delay_d=19 # milliseconds between frames playback, if echo sounds bad, adjust this up or down 1 or 2 ms diff --git a/exec_G.sh b/exec_G.sh new file mode 100755 index 0000000..225d4d2 --- /dev/null +++ b/exec_G.sh @@ -0,0 +1,4 @@ +#!/bin/sh +qnvoice ${2} gatewayrestart.dat 'Gateway Restart' +sleep 5 +systemctl restart qngateway diff --git a/exec_H.sh b/exec_H.sh index ee14f1e..9fc303e 100755 --- a/exec_H.sh +++ b/exec_H.sh @@ -1,2 +1,4 @@ #!/bin/sh -halt +qnvoice ${2} shutdown.dat 'System Shutdown' +sleep 5 +shutdown -h now diff --git a/exec_R.sh b/exec_R.sh index d7cfcc0..db73a28 100755 --- a/exec_R.sh +++ b/exec_R.sh @@ -1,2 +1,4 @@ #!/bin/sh -reboot +qnvoice ${2} rebooting.dat 'System Reboot' +sleep 5 +shutdown -r now diff --git a/get_gwy_list.sh b/get_gwy_list.sh deleted file mode 100755 index d6de493..0000000 --- a/get_gwy_list.sh +++ /dev/null @@ -1,5 +0,0 @@ -#/bin/bash - -mv -f gwys.txt gwys.txt.bak - -wget http://www.va3uv.com/gwys.txt diff --git a/get_reflectors.sh b/get_reflectors.sh deleted file mode 100755 index fe62c66..0000000 --- a/get_reflectors.sh +++ /dev/null @@ -1,30 +0,0 @@ -#/bin/bash - -# Get the big list from Ramesh (VA3UV) and extract the DCS, DStar and XReflectors only. -# Put XREF reflectors on port 20001 so they will use DPlus linking! -# -# 73 -# -# Tom, n7tae (at) arrl (dot) net - -if [ -e gwys.txt ]; then - mv -f gwys.txt gwys.txt.orig -fi - -rm -f gwys.va2uv.txt - -wget -nv -O gwys.va3uv.txt http://www.va3uv.com/gwys.txt - -if [ -e gwys.va3uv.txt ]; then - echo "# from www.va3uv.com on `date`" > gwys.txt - echo "Got `awk '$1~/^REF/{print $1, $2, $3}' gwys.va3uv.txt | tee -a gwys.txt | wc -l` REF reflectors" - # Move DPlus and DExtra to port 20001 - echo "Got `awk '$1~/^XRF/{print $1, $2, 20001}' gwys.va3uv.txt | tee -a gwys.txt | wc -l` XRF reflectors" - echo "Got `awk '$1~/^DCS/{print $1, $2, $3}' gwys.va3uv.txt | tee -a gwys.txt | wc -l` DCS reflectors" -else - echo "Could not get gateways list from www.va3uv.com!" - if [ -e gwys.txt.orig ]; then - mv -f gwys.txt.orig gwys.txt - fi -fi - diff --git a/ircddb/IRCApplication.h b/ircddb/IRCApplication.h index 92b7018..5e7d6c2 100644 --- a/ircddb/IRCApplication.h +++ b/ircddb/IRCApplication.h @@ -23,6 +23,9 @@ public: virtual void setSendQ(IRCMessageQueue *s) = 0; virtual IRCMessageQueue *getSendQ(void) = 0; + virtual void putReplyMessage(IRCMessage *m) = 0; + virtual void sendPing(const std::string &to, const std::string &from) = 0; + virtual ~IRCApplication() {} }; diff --git a/ircddb/IRCClient.cpp b/ircddb/IRCClient.cpp index 83b17d6..fe72db9 100644 --- a/ircddb/IRCClient.cpp +++ b/ircddb/IRCClient.cpp @@ -7,11 +7,8 @@ #include "IRCClient.h" #include "IRCutils.h" -#include -#include -IRCClient::IRCClient(IRCApplication *app, const std::string &update_channel, const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, - const std::string &versionInfo, const std::string &localAddr) +IRCClient::IRCClient(IRCApplication *app, const std::string &update_channel, const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, const std::string &versionInfo) { safeStringCopy(host_name, hostName.c_str(), sizeof host_name); @@ -23,11 +20,6 @@ IRCClient::IRCClient(IRCApplication *app, const std::string &update_channel, con this->app = app; - if (localAddr.empty()) - safeStringCopy(local_addr, "0.0.0.0", sizeof local_addr); - else - safeStringCopy(local_addr, localAddr.c_str(), sizeof local_addr); - proto = new IRCProtocol(app, this->callsign, password, update_channel, versionInfo); recvQ = NULL; @@ -58,26 +50,11 @@ void IRCClient::stopWork() #define MAXIPV4ADDR 10 void IRCClient::Entry() { - - unsigned int numAddr; - - struct sockaddr_in addr[MAXIPV4ADDR]; - - struct sockaddr_in myaddr; + CTCPReaderWriterClient ircSock; int state = 0; int timer = 0; - int sock = 0; - unsigned int currentAddr = 0; - - numAddr = 0; - - int result = getAllIPV4Addresses(local_addr, 0, &numAddr, &myaddr, 1); - - if ((result != 0) || (numAddr != 1)) { - printf("IRCClient::Entry: local address not parseable, using 0.0.0.0\n"); - memset(&myaddr, 0x00, sizeof(struct sockaddr_in)); - } + socklen_t optlen; while (true) { @@ -95,159 +72,27 @@ void IRCClient::Entry() if (timer == 0) { timer = 30; - if (getAllIPV4Addresses(host_name, port, &numAddr, addr, MAXIPV4ADDR) == 0) { - //printf("IRCClient::Entry: number of DNS entries %d\n", numAddr); - if (numAddr > 0) { - currentAddr = 0; - state = 1; - timer = 0; - } - } - } - break; - - case 1: - if (terminateThread) { - printf("IRCClient::Entry: thread terminated at state=%d\n", state); - return; - } - - if (timer == 0) { - sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); - - if (sock < 0) { - printf("IRCClient::Entry: could not create socket!\n"); - timer = 30; - state = 0; - } else { - if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) { - printf("IRCClient::Entry: fcntl error\n"); - close(sock); - timer = 30; - state = 0; - } - else { - unsigned char *h = (unsigned char *) &(myaddr.sin_addr); - int res; - - if ((h[0] != 0) || (h[1] != 0) || (h[2] != 0) || (h[3] != 0)) - printf("IRCClient::Entry: bind: local address %d.%d.%d.%d\n", h[0], h[1], h[2], h[3]); - - res = bind(sock, (struct sockaddr *) &myaddr, sizeof (struct sockaddr_in)); - - if (res != 0) { - printf("IRCClient::Entry: bind error\n"); - close(sock); - state = 0; - timer = 30; - break; - } - - - h = (unsigned char *) &(addr[currentAddr].sin_addr); - //printf("IRCClient::Entry: trying to connect to %d.%d.%d.%d\n", h[0], h[1], h[2], h[3]); - - res = connect(sock, (struct sockaddr *) (addr + currentAddr), sizeof (struct sockaddr_in)); - - if (res == 0) { - printf("IRCClient::Entry: connected to %d.%d.%d.%d\n", h[0], h[1], h[2], h[3]); - state = 4; - } else { - if (errno == EINPROGRESS) { - //printf("IRCClient::Entry: connect in progress\n"); - state = 3; - timer = 10; // 5 second timeout - } else { - printf("IRCClient::Entry: connect\n"); - close(sock); - currentAddr++; - if (currentAddr >= numAddr) { - state = 0; - timer = 30; - } else { - state = 1; - timer = 4; - } - } - } - } // connect + if (! ircSock.Open(host_name, AF_UNSPEC, std::to_string(port))) { + state = 4; + timer = 0; } } break; - case 3: { - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 0; - fd_set myset; - FD_ZERO(&myset); - FD_SET(sock, &myset); - int res = select(sock+1, NULL, &myset, NULL, &tv); - - if (res < 0) { - printf("IRCClient::Entry: select\n"); - close(sock); - state = 0; - timer = 30; - } else if (res > 0) { // connect is finished - socklen_t val_len; - int value; - - val_len = sizeof value; - - if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *) &value, &val_len) < 0) { - printf("IRCClient::Entry: getsockopt error\n"); - close(sock); - state = 0; - timer = 30; - } else { - if (value != 0) { - printf("IRCClient::Entry: SO_ERROR=%d\n", value); - close(sock); - currentAddr ++; - if (currentAddr >= numAddr) { - state = 0; - timer = 30; - } else { - state = 1; - timer = 2; - } - } else { - printf("IRCClient::Entry: connected2\n"); - state = 4; - } - } - } else if (timer == 0) { - // select timeout and timer timeout - //printf("IRCClient::Entry: connect timeout\n"); - close(sock); - currentAddr++; - if (currentAddr >= numAddr) { - state = 0; - timer = 30; - } else { - state = 1; // open new socket - timer = 2; - } - } - - } - break; - - case 4: { + case 4: + optlen = sizeof(int); + getsockopt(ircSock.GetFD(), SOL_SOCKET, SO_DOMAIN, &family, &optlen); recvQ = new IRCMessageQueue(); sendQ = new IRCMessageQueue(); - recv = new IRCReceiver(sock, recvQ); + recv = new IRCReceiver(&ircSock, recvQ); recv->startWork(); proto->setNetworkReady(true); state = 5; timer = 0; - - } - break; + break; case 5: @@ -275,18 +120,12 @@ void IRCClient::Entry() int len = strlen(buf); if (buf[len - 1] == 10) { // is there a NL char at the end? - int r = send(sock, buf, len, 0); - - if (r != len) { - printf("IRCClient::Entry: short write %d < %d\n", r, len); + if (ircSock.Write((unsigned char *)buf, len)) { + printf("IRCClient::Entry: short write\n"); timer = 0; state = 6; } - /* else - { - printf("write %d bytes (%s)\n", len, out.c_str()); - } */ } else { printf("IRCClient::Entry: no NL at end, len=%d\n", len); @@ -314,7 +153,7 @@ void IRCClient::Entry() delete recvQ; delete sendQ; - close(sock); + ircSock.Close(); if (terminateThread) { // request to end the thread printf("IRCClient::Entry: thread terminated at state=%d\n", state); @@ -331,3 +170,8 @@ void IRCClient::Entry() } return; } + +int IRCClient::GetFamily() +{ + return family; +} diff --git a/ircddb/IRCClient.h b/ircddb/IRCClient.h index f79f711..e6f58ba 100644 --- a/ircddb/IRCClient.h +++ b/ircddb/IRCClient.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include "../TCPReaderWriterClient.h" #include "IRCReceiver.h" #include "IRCMessageQueue.h" @@ -10,17 +12,19 @@ class IRCClient { public: - IRCClient(IRCApplication *app, const std::string &update_channel, const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, - const std::string &versionInfo, const std::string &localAddr); + IRCClient(IRCApplication *app, const std::string &update_channel, const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, const std::string &versionInfo); virtual ~IRCClient(); bool startWork(); void stopWork(); + int GetFamily(); protected: virtual void Entry(); private: + std::atomic family; + std::future client_thread; char host_name[100]; char local_addr[100]; unsigned int port; @@ -33,7 +37,6 @@ private: IRCMessageQueue *recvQ; IRCMessageQueue *sendQ; IRCProtocol *proto; - std::future client_thread; IRCApplication *app; }; diff --git a/ircddb/IRCDDB.cpp b/ircddb/IRCDDB.cpp index 94966c4..9c076b7 100644 --- a/ircddb/IRCDDB.cpp +++ b/ircddb/IRCDDB.cpp @@ -9,8 +9,7 @@ struct CIRCDDBPrivate { IRCDDBApp *app; }; - -CIRCDDB::CIRCDDB(const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, const std::string &versionInfo, const std::string &localAddr) +CIRCDDB::CIRCDDB(const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, const std::string &versionInfo) : d(new CIRCDDBPrivate) { @@ -18,7 +17,7 @@ CIRCDDB::CIRCDDB(const std::string &hostName, unsigned int port, const std::stri d->app = new IRCDDBApp(update_channel); - d->client = new IRCClient(d->app, update_channel, hostName, port, callsign, password, versionInfo, localAddr); + d->client = new IRCClient(d->app, update_channel, hostName, port, callsign, password, versionInfo); } CIRCDDB::~CIRCDDB() @@ -28,11 +27,16 @@ CIRCDDB::~CIRCDDB() delete d; } +int CIRCDDB::GetFamily() +{ + return d->client->GetFamily(); +} + // A false return implies a network error, or unable to log in bool CIRCDDB::open() { - printf("start"); + printf("starting CIRCDDB\n"); return d->client->startWork() && d->app->startWork(); } @@ -61,31 +65,30 @@ void CIRCDDB::kickWatchdog(const std::string &wdInfo) } // Send heard data, a false return implies a network error -bool CIRCDDB::sendHeard(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, - const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3) +bool CIRCDDB::sendHeard(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3) { if (myCall.size() != 8) { - printf("CIRCDDB::sendHeard:myCall: len != 8"); + printf("CIRCDDB::sendHeard:myCall: len != 8\n"); return false; } if (myCallExt.size() != 4) { - printf("CIRCDDB::sendHeard:myCallExt: len != 4"); + printf("CIRCDDB::sendHeard:myCallExt: len != 4\n"); return false; } if (yourCall.size() != 8) { - printf("CIRCDDB::sendHeard:yourCall: len != 8"); + printf("CIRCDDB::sendHeard:yourCall: len != 8\n"); return false; } if (rpt1.size() != 8) { - printf("CIRCDDB::sendHeard:rpt1: len != 8"); + printf("CIRCDDB::sendHeard:rpt1: len != 8\n"); return false; } if (rpt2.size() != 8) { - printf("CIRCDDB::sendHeard:rpt2: len != 8"); + printf("CIRCDDB::sendHeard:rpt2: len != 8\n"); return false; } @@ -93,31 +96,30 @@ bool CIRCDDB::sendHeard(const std::string &myCall, const std::string &myCallExt, } // Send heard data, a false return implies a network error -bool CIRCDDB::sendHeardWithTXMsg(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, - unsigned char flag2, unsigned char flag3, const std::string &network_destination, const std::string &tx_message) +bool CIRCDDB::sendHeardWithTXMsg(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3, const std::string &network_destination, const std::string &tx_message) { if (myCall.size() != 8) { - printf("CIRCDDB::sendHeard:myCall: len != 8"); + printf("CIRCDDB::sendHeard:myCall: len != 8\n"); return false; } if (myCallExt.size() != 4) { - printf("CIRCDDB::sendHeard:myCallExt: len != 4"); + printf("CIRCDDB::sendHeard:myCallExt: len != 4\n"); return false; } if (yourCall.size() != 8) { - printf("CIRCDDB::sendHeard:yourCall: len != 8"); + printf("CIRCDDB::sendHeard:yourCall: len != 8\n"); return false; } if (rpt1.size() != 8) { - printf("CIRCDDB::sendHeard:rpt1: len != 8"); + printf("CIRCDDB::sendHeard:rpt1: len != 8\n"); return false; } if (rpt2.size() != 8) { - printf("CIRCDDB::sendHeard:rpt2: len != 8"); + printf("CIRCDDB::sendHeard:rpt2: len != 8\n"); return false; } @@ -127,7 +129,7 @@ bool CIRCDDB::sendHeardWithTXMsg(const std::string &myCall, const std::string &m dest = " "; if (dest.size() != 8) { - printf("CIRCDDB::sendHeard:network_destination: len != 8"); + printf("CIRCDDB::sendHeard:network_destination: len != 8\n"); return false; } @@ -148,48 +150,46 @@ bool CIRCDDB::sendHeardWithTXMsg(const std::string &myCall, const std::string &m return d->app->sendHeard( myCall, myCallExt, yourCall, rpt1, rpt2, flag1, flag2, flag3, dest, msg, ""); } - - bool CIRCDDB::sendHeardWithTXStats(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3, int num_dv_frames, int num_dv_silent_frames, int num_bit_errors) { if (num_dv_frames<= 0 || num_dv_frames>65535) { - printf("CIRCDDB::sendHeard:num_dv_frames not in range 1-65535"); + printf("CIRCDDB::sendHeard:num_dv_frames not in range 1-65535\n"); return false; } if (num_dv_silent_frames > num_dv_frames) { - printf("CIRCDDB::sendHeard:num_dv_silent_frames > num_dv_frames"); + printf("CIRCDDB::sendHeard:num_dv_silent_frames > num_dv_frames\n"); return false; } if (num_bit_errors > 4*num_dv_frames) { // max 4 bit errors per frame - printf("CIRCDDB::sendHeard:num_bit_errors > (4*num_dv_frames)"); + printf("CIRCDDB::sendHeard:num_bit_errors > (4*num_dv_frames)\n"); return false; } if (myCall.size() != 8) { - printf("CIRCDDB::sendHeard:myCall: len != 8"); + printf("CIRCDDB::sendHeard:myCall: len != 8\n"); return false; } if (myCallExt.size() != 4) { - printf("CIRCDDB::sendHeard:myCallExt: len != 4"); + printf("CIRCDDB::sendHeard:myCallExt: len != 4\n"); return false; } if (yourCall.size() != 8) { - printf("CIRCDDB::sendHeard:yourCall: len != 8"); + printf("CIRCDDB::sendHeard:yourCall: len != 8\n"); return false; } if (rpt1.size() != 8) { - printf("CIRCDDB::sendHeard:rpt1: len != 8"); + printf("CIRCDDB::sendHeard:rpt1: len != 8\n"); return false; } if (rpt2.size() != 8) { - printf("CIRCDDB::sendHeard:rpt2: len != 8"); + printf("CIRCDDB::sendHeard:rpt2: len != 8\n"); return false; } @@ -216,13 +216,11 @@ bool CIRCDDB::sendHeardWithTXStats(const std::string &myCall, const std::string return d->app->sendHeard( myCall, myCallExt, yourCall, rpt1, rpt2, flag1, flag2, flag3, " ", "", stats); } - - // Send query for a gateway/reflector, a false return implies a network error bool CIRCDDB::findGateway(const std::string &gatewayCallsign) { if (gatewayCallsign.size() != 8) { - printf("CIRCDDB::findGateway: len != 8"); + printf("CIRCDDB::findGateway: len != 8\n"); return false; } std::string gcs = gatewayCallsign; @@ -230,11 +228,10 @@ bool CIRCDDB::findGateway(const std::string &gatewayCallsign) return d->app->findGateway(gcs); } - bool CIRCDDB::findRepeater(const std::string &repeaterCallsign) { if (repeaterCallsign.size() != 8) { - printf("CIRCDDB::findRepeater: len != 8"); + printf("CIRCDDB::findRepeater: len != 8\n"); return false; } std::string rcs = repeaterCallsign; @@ -246,7 +243,7 @@ bool CIRCDDB::findRepeater(const std::string &repeaterCallsign) bool CIRCDDB::findUser(const std::string &userCallsign) { if (userCallsign.size() != 8) { - printf("CIRCDDB::findUser: len != 8"); + printf("CIRCDDB::findUser: len != 8\n"); return false; } std::string ucs = userCallsign; @@ -269,24 +266,24 @@ bool CIRCDDB::receiveRepeater(std::string &repeaterCallsign, std::string &gatewa IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType(); if (rt != IDRT_REPEATER) { - printf("CIRCDDB::receiveRepeater: unexpected response type"); + printf("CIRCDDB::receiveRepeater: unexpected response type\n"); return false; } - IRCMessage * m = d->app->getReplyMessage(); + IRCMessage *m = d->app->getReplyMessage(); if (m == NULL) { - printf("CIRCDDB::receiveRepeater: no message"); + printf("CIRCDDB::receiveRepeater: no message\n"); return false; } if (m->getCommand().compare("IDRT_REPEATER")) { - printf("CIRCDDB::receiveRepeater: wrong message type"); + printf("CIRCDDB::receiveRepeater: wrong message type\n"); return false; } if (m->getParamCount() != 3) { - printf("CIRCDDB::receiveRepeater: unexpected number of message parameters"); + printf("CIRCDDB::receiveRepeater: unexpected number of message parameters\n"); return false; } @@ -306,24 +303,24 @@ bool CIRCDDB::receiveGateway(std::string &gatewayCallsign, std::string &address, IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType(); if (rt != IDRT_GATEWAY) { - printf("CIRCDDB::receiveGateway: unexpected response type"); + printf("CIRCDDB::receiveGateway: unexpected response type\n"); return false; } - IRCMessage * m = d->app->getReplyMessage(); + IRCMessage *m = d->app->getReplyMessage(); if (m == NULL) { - printf("CIRCDDB::receiveGateway: no message"); + printf("CIRCDDB::receiveGateway: no message\n"); return false; } if (m->getCommand().compare("IDRT_GATEWAY")) { - printf("CIRCDDB::receiveGateway: wrong message type"); + printf("CIRCDDB::receiveGateway: wrong message type\n"); return false; } if (m->getParamCount() != 2) { - printf("CIRCDDB::receiveGateway: unexpected number of message parameters"); + printf("CIRCDDB::receiveGateway: unexpected number of message parameters\n"); return false; } @@ -343,30 +340,29 @@ bool CIRCDDB::receiveUser(std::string &userCallsign, std::string &repeaterCallsi return receiveUser(userCallsign, repeaterCallsign, gatewayCallsign, address, dummy); } -bool CIRCDDB::receiveUser(std::string &userCallsign, std::string &repeaterCallsign, std::string &gatewayCallsign, std::string &address, - std::string &timeStamp) +bool CIRCDDB::receiveUser(std::string &userCallsign, std::string &repeaterCallsign, std::string &gatewayCallsign, std::string &address, std::string &timeStamp) { IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType(); if (rt != IDRT_USER) { - printf("CIRCDDB::receiveUser: unexpected response type"); + printf("CIRCDDB::receiveUser: unexpected response type\n"); return false; } - IRCMessage * m = d->app->getReplyMessage(); + IRCMessage *m = d->app->getReplyMessage(); if (m == NULL) { - printf("CIRCDDB::receiveUser: no message"); + printf("CIRCDDB::receiveUser: no message\n"); return false; } if (m->getCommand().compare("IDRT_USER")) { - printf("CIRCDDB::receiveUser: wrong message type"); + printf("CIRCDDB::receiveUser: wrong message type\n"); return false; } if (m->getParamCount() != 5) { - printf("CIRCDDB::receiveUser: unexpected number of message parameters"); + printf("CIRCDDB::receiveUser: unexpected number of message parameters\n"); return false; } @@ -381,9 +377,46 @@ bool CIRCDDB::receiveUser(std::string &userCallsign, std::string &repeaterCallsi return true; } +bool CIRCDDB::receivePing(std::string &repeaterCallsign) +{ + IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType(); + + if (rt != IDRT_PING) { + printf("CIRCDDB::receivePing: unexpected response type\n"); + return false; + } + + IRCMessage *m = d->app->getReplyMessage(); + + if (NULL == m) { + printf("CIRCDDB::receivePing: no message\n"); + return false; + } + + if (m->getCommand().compare("IDRT_PING")) { + printf("CIRCDDB::receivePing: wrong messsage type\n"); + return false; + } + + if (1 != m->getParamCount()) { + printf("CIRCDDB::receivePing: unexpected number of message parameters\n"); + return false; + } + + repeaterCallsign = m->getParam(0); + + delete m; + + return true; +} + +void CIRCDDB::sendPing(const std::string &to, const std::string &from) +{ + d->app->sendPing(to, from); +} + void CIRCDDB::close() // Implictely kills any threads in the IRC code { d->client->stopWork(); d->app->stopWork(); } - diff --git a/ircddb/IRCDDB.h b/ircddb/IRCDDB.h index af2691b..4ba7858 100644 --- a/ircddb/IRCDDB.h +++ b/ircddb/IRCDDB.h @@ -6,7 +6,8 @@ enum IRCDDB_RESPONSE_TYPE { IDRT_NONE, IDRT_USER, IDRT_GATEWAY, - IDRT_REPEATER + IDRT_REPEATER, + IDRT_PING }; enum DSTAR_PROTOCOL { @@ -15,19 +16,20 @@ enum DSTAR_PROTOCOL { DP_DPLUS }; - struct CIRCDDBPrivate; class CIRCDDB { public: - CIRCDDB(const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, const std::string &versionInfo, const std::string &localAddr = ""); + CIRCDDB(const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, const std::string &versionInfo); ~CIRCDDB(); + // returns the socket family type + int GetFamily(); + // A false return implies a network error, or unable to log in bool open(); - // rptrQTH can be called multiple times if necessary // rptrcall callsign of the repeater // latitude WGS84 position of antenna in degrees, positive value -> NORTH @@ -36,8 +38,6 @@ public: void rptrQTH(const std::string &rptrcall, double latitude, double longitude, const std::string &desc1, const std::string &desc2, const std::string &infoURL, const std::string &swVersion); - - // rptrQRG can be called multiple times if necessary // module letter of the module, valid values: "A", "B", "C", "D", "AD", "BD", "CD", "DD" // txFrequency repeater TX frequency in MHz @@ -47,7 +47,6 @@ public: void rptrQRG(const std::string &rptrcall, double txFrequency, double duplexShift, double range, double agl); - // If you call this method once, watchdog messages will be sent to the // to the ircDDB network every 15 minutes. Invoke this method every 1-2 minutes to indicate // that the gateway is working properly. After activating the watchdog, a red LED will be displayed @@ -58,8 +57,6 @@ public: void kickWatchdog(const std::string &wdInfo); - - // get internal network status int getConnectionState(); // one of these values is returned: @@ -72,14 +69,12 @@ public: // Send heard data, a false return implies a network error bool sendHeard(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3); - // same as sendHeard with two new fields: // network_destination: empty string or 8-char call sign of the repeater // or reflector, where this transmission is relayed to. // tx_message: 20-char TX message or empty string, if the user did not // send a TX message - bool sendHeardWithTXMsg(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, - unsigned char flag2, unsigned char flag3, const std::string &network_destination, const std::string &tx_message); + bool sendHeardWithTXMsg(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3, const std::string &network_destination, const std::string &tx_message); // this method should be called at the end of a transmission // num_dv_frames: number of DV frames sent out (96 bit frames, 20ms) @@ -91,8 +86,7 @@ public: // So, the overall bit error rate is calculated like this: // BER = num_bit_errors / (num_dv_frames * 24) // Set num_bit_errors = -1, if the error information is not available. - bool sendHeardWithTXStats(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, - unsigned char flag2, unsigned char flag3, int num_dv_frames, int num_dv_silent_frames, int num_bit_errors); + bool sendHeardWithTXStats(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3, int num_dv_frames, int num_dv_silent_frames, int num_bit_errors); // The following three functions don't block waiting for a reply, they just send the data @@ -124,10 +118,12 @@ public: bool receiveUser(std::string &userCallsign, std::string &repeaterCallsign, std::string &gatewayCallsign, std::string &address, std::string &timeStamp); - void close(); // Implictely kills any threads in the IRC code + bool receivePing(std::string &repeaterCallsign); + void sendPing(const std::string &to, const std::string &from); + + void close(); // Implictely kills any threads in the IRC code private: struct CIRCDDBPrivate * const d; - }; diff --git a/ircddb/IRCDDBApp.cpp b/ircddb/IRCDDBApp.cpp index 340629a..d20a30f 100644 --- a/ircddb/IRCDDBApp.cpp +++ b/ircddb/IRCDDBApp.cpp @@ -253,6 +253,8 @@ IRCDDB_RESPONSE_TYPE IRCDDBApp::getReplyMessageType() return IDRT_REPEATER; } else if (msgType == std::string("IDRT_GATEWAY")) { return IDRT_GATEWAY; + } else if (msgType == std::string("IDRT_PING")) { + return IDRT_PING; } printf("IRCDDBApp::getMessageType: unknown msg type: %s\n", msgType.c_str()); @@ -265,6 +267,11 @@ IRCMessage *IRCDDBApp::getReplyMessage() return d->replyQ.getMessage(); } +void IRCDDBApp::putReplyMessage(IRCMessage *m) +{ + d->replyQ.putMessage(m); +} + bool IRCDDBApp::startWork() { d->terminateThread = false; @@ -428,6 +435,37 @@ void IRCDDBApp::userChanOp(const std::string &nick, bool op) d->userMapMutex.unlock(); } +void IRCDDBApp::sendPing(const std::string &to, const std::string &from) +{ + std::string t = to.substr(0, 7); + + ReplaceChar(t, '_', ' '); + while (isspace(t[t.length()-1])) + t.pop_back(); + ToLower(t); + + d->userMapMutex.lock(); + for (int j=1; j <= 4; j++) { + std::string ircUser = t + std::string("-") + std::to_string(j); + + if (1 == d->user.count(ircUser)) { + std::string f(from); + ReplaceChar(f, ' ', '_'); + IRCMessage *rm = new IRCMessage(ircUser, "IDRT_PING"); + rm->addParam(f); + std::string out; + rm->composeMessage(out); + out.pop_back(); + out.pop_back(); + printf("IRCDDBApp::sendPing: %s\n", out.c_str()); + d->sendQ->putMessage(rm); + break; + } + } + d->userMapMutex.unlock(); + +} + static const int numberOfTables = 2; std::string IRCDDBApp::getIPAddress(std::string &zonerp_cs) @@ -472,48 +510,8 @@ bool IRCDDBApp::findGateway(const std::string &gwCall) return true; } -static void findReflector(const std::string &rptrCall, IRCDDBAppPrivate *d) -{ - std::string zonerp_cs; - std::string ipAddr; - -#define MAXIPV4ADDR 5 - struct sockaddr_in addr[MAXIPV4ADDR]; - unsigned int numAddr = 0; - - char host_name[80]; - - std::string host = rptrCall.substr(0,6) + ".reflector.ircddb.net"; - - safeStringCopy(host_name, host.c_str(), sizeof host_name); - - if (getAllIPV4Addresses(host_name, 0, &numAddr, addr, MAXIPV4ADDR) == 0) { - if (numAddr > 0) { - unsigned char *a = (unsigned char *) &addr[0].sin_addr; - char buf[16]; - snprintf(buf, 16, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]); - ipAddr = buf; - zonerp_cs = rptrCall; - zonerp_cs[7] = 'G'; - } - } - - - IRCMessage *m2 = new IRCMessage("IDRT_REPEATER"); - m2->addParam(rptrCall); - m2->addParam(zonerp_cs); - m2->addParam(ipAddr); - d->replyQ.putMessage(m2); -} - bool IRCDDBApp::findRepeater(const std::string &rptrCall) { - - if (0==rptrCall.compare(0, 3, "XRF") || 0==rptrCall.compare(0, 3, "REF")) { - findReflector(rptrCall, d); - return true; - } - std::string arearp_cs = rptrCall; ReplaceChar(arearp_cs, ' ', '_'); diff --git a/ircddb/IRCDDBApp.h b/ircddb/IRCDDBApp.h index 09bdf7c..1be0ffd 100644 --- a/ircddb/IRCDDBApp.h +++ b/ircddb/IRCDDBApp.h @@ -33,6 +33,9 @@ public: virtual void setSendQ(IRCMessageQueue *s); virtual IRCMessageQueue *getSendQ(); + virtual void putReplyMessage(IRCMessage *m); + virtual void sendPing(const std::string &to, const std::string &from); + bool startWork(); void stopWork(); @@ -44,9 +47,7 @@ public: bool findRepeater(const std::string &s); bool findGateway(const std::string &s); - bool sendHeard(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, - const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3, - const std::string &destination, const std::string &tx_msg, const std::string &tx_stats); + bool sendHeard(const std::string &myCall, const std::string &myCallExt, const std::string &yourCall, const std::string &rpt1, const std::string &rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3, const std::string &destination, const std::string &tx_msg, const std::string &tx_stats); int getConnectionState(); diff --git a/ircddb/IRCProtocol.cpp b/ircddb/IRCProtocol.cpp index eb2837a..cf7fa2c 100644 --- a/ircddb/IRCProtocol.cpp +++ b/ircddb/IRCProtocol.cpp @@ -10,6 +10,7 @@ IRCProtocol::IRCProtocol(IRCApplication *app, const std::string &callsign, const std::string &password, const std::string &channel, const std::string &versionInfo) { + srand(time(NULL)); this->password = password; this->channel = channel; this->app = app; @@ -175,11 +176,22 @@ bool IRCProtocol::processQueues(IRCMessageQueue *recvQ, IRCMessageQueue *sendQ) } } } else if (0 == m->command.compare("PRIVMSG")) { - if ((m->numParams == 2) && (app != NULL)) { - if (0 == m->params[0].compare(channel)) { - app->msgChannel(m); - } else if (0 == m->params[0].compare(currentNick)) { - app->msgQuery(m); + if (app) { + std::string out; + m->composeMessage(out); + out.pop_back(); out.pop_back(); + if (2 == m->numParams) { + if (0 == m->params[0].compare(channel)) { + app->msgChannel(m); + } else if (0 == m->params[0].compare(currentNick)) { + if (0 == m->params[1].find("IDRT_PING")) { + std::string from = m->params[1].substr(10); + IRCMessage *rm = new IRCMessage("IDRT_PING"); + rm->addParam(from); + app->putReplyMessage(rm); + } else + app->msgQuery(m); + } } } } else if (0 == m->command.compare("352")) { // WHO list @@ -342,5 +354,3 @@ bool IRCProtocol::processQueues(IRCMessageQueue *recvQ, IRCMessageQueue *sendQ) return true; } - - diff --git a/ircddb/IRCReceiver.cpp b/ircddb/IRCReceiver.cpp index f2c0bcf..25d0a59 100644 --- a/ircddb/IRCReceiver.cpp +++ b/ircddb/IRCReceiver.cpp @@ -6,7 +6,7 @@ #include "IRCMessage.h" #include "IRCReceiver.h" -static int doRead(int sock, char * buf, int buf_size) +static int doRead(CTCPReaderWriterClient *ircSock, char *buf, int buf_size) { struct timeval tv; tv.tv_sec = 1; @@ -14,26 +14,27 @@ static int doRead(int sock, char * buf, int buf_size) fd_set rdset; fd_set errset; + int fd = ircSock->GetFD(); FD_ZERO(&rdset); FD_ZERO(&errset); - FD_SET(sock, &rdset); - FD_SET(sock, &errset); + FD_SET(fd, &rdset); + FD_SET(fd, &errset); int res; - res = select(sock+1, &rdset, NULL, &errset, &tv); + res = select(fd+1, &rdset, NULL, &errset, &tv); if ( res < 0 ) { printf("IRCReceiver::doread: select() error.\n"); return -1; } else if ( res > 0 ) { - if (FD_ISSET(sock, &errset)) { + if (FD_ISSET(fd, &errset)) { printf("IRCReceiver::doRead: FD_ISSET error\n"); return -1; } - if (FD_ISSET(sock, &rdset)) { - res = recv(sock, buf, buf_size, 0); + if (FD_ISSET(fd, &rdset)) { + res = ircSock->Read((unsigned char *)buf, buf_size); if (res < 0) { printf("IRCReceiver::doRead: recv error\n"); @@ -59,7 +60,7 @@ void IRCReceiver::Entry() while (!terminateThread) { char buf[200]; - int r = doRead(sock, buf, sizeof buf); + int r = doRead(ircSock, buf, sizeof buf); if (r < 0) { recvQ->signalEOF(); @@ -133,9 +134,9 @@ void IRCReceiver::Entry() return; } -IRCReceiver::IRCReceiver(int sock, IRCMessageQueue *q) +IRCReceiver::IRCReceiver(CTCPReaderWriterClient *sock, IRCMessageQueue *q) { - this->sock = sock; + ircSock = sock; recvQ = q; } diff --git a/ircddb/IRCReceiver.h b/ircddb/IRCReceiver.h index 8f5323f..e1241cd 100644 --- a/ircddb/IRCReceiver.h +++ b/ircddb/IRCReceiver.h @@ -1,11 +1,12 @@ #pragma once #include #include "IRCMessageQueue.h" +#include "../TCPReaderWriterClient.h" class IRCReceiver { public: - IRCReceiver(int sock, IRCMessageQueue *q); + IRCReceiver(CTCPReaderWriterClient *ircSock, IRCMessageQueue *q); virtual ~IRCReceiver(); bool startWork(); void stopWork(); @@ -14,6 +15,7 @@ protected: virtual void Entry(); private: + CTCPReaderWriterClient *ircSock; bool terminateThread; int sock; IRCMessageQueue *recvQ; diff --git a/ircddb/IRCutils.cpp b/ircddb/IRCutils.cpp index 9076b66..c844c94 100644 --- a/ircddb/IRCutils.cpp +++ b/ircddb/IRCutils.cpp @@ -35,84 +35,6 @@ std::vector stringTokenizer(const std::string &s) return result; } -int getAllIPV4Addresses(const char * name, unsigned short port, unsigned int * num, struct sockaddr_in * addr, unsigned int max_addr) -{ - - struct addrinfo hints; - struct addrinfo * res; - - memset(&hints, 0x00, sizeof(struct addrinfo)); - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - - int r = getaddrinfo(name, NULL, &hints, &res); - - if (r == 0) { - struct addrinfo * rp; - unsigned int numAddr = 0; - - for (rp = res; rp != NULL; rp = rp->ai_next) { - if (rp->ai_family == AF_INET) - numAddr ++; - } - - if (numAddr > 0) { - if (numAddr > max_addr) - numAddr = max_addr; - - int * shuffle = new int[numAddr]; - - unsigned int i; - - for (i=0; i < numAddr; i++) - shuffle[i] = i; - - for (i=0; i < (numAddr - 1); i++) { - if (rand() & 1) { - int tmp; - tmp = shuffle[i]; - shuffle[i] = shuffle[i+1]; - shuffle[i+1] = tmp; - } - } - - for (i=(numAddr - 1); i > 0; i--) { - if (rand() & 1) { - int tmp; - tmp = shuffle[i]; - shuffle[i] = shuffle[i-1]; - shuffle[i-1] = tmp; - } - } - - for (rp = res, i=0 ; (rp != NULL) && (i < numAddr); rp = rp->ai_next) { - if (rp->ai_family == AF_INET) { - memcpy( addr+shuffle[i], rp->ai_addr, sizeof (struct sockaddr_in) ); - - addr[shuffle[i]].sin_port = htons(port); - - i++; - } - } - - delete[] shuffle; - } - - *num = numAddr; - - freeaddrinfo(res); - - return 0; - - } else { - printf("getaddrinfo: %s\n", gai_strerror(r)); - - return 1; - } - - -} - void safeStringCopy (char *dest, const char *src, unsigned int buf_size) { unsigned int i = 0; diff --git a/ircddb/IRCutils.h b/ircddb/IRCutils.h index d139ed6..b2fef99 100644 --- a/ircddb/IRCutils.h +++ b/ircddb/IRCutils.h @@ -27,8 +27,6 @@ time_t parseTime(const std::string str); std::vector stringTokenizer(const std::string &str); -int getAllIPV4Addresses(const char *name, unsigned short port, unsigned int *num, struct sockaddr_in *addr, unsigned int max_addr); - void safeStringCopy(char * dest, const char * src, unsigned int buf_size); char *getCurrentTime(void); diff --git a/qn.dvap.cfg b/qn.dvap.cfg index 835c675..75b4dfe 100644 --- a/qn.dvap.cfg +++ b/qn.dvap.cfg @@ -1,32 +1,9 @@ -# g2_ircddb Configuration for me +# A Simple Configuration for a 2M DVAP -ircddb = { - login = "XX0XXX" -# If you are not using rr.openquad.net, you need to specify the host and possibly the password. -# -# host = "some.server.host" // others include group1-irc.ircddb.net -# password = "1111111111111" // not needed for rr.openquad.net -} +ircddb_login='Q1ABC' -module = { - c = { // change the module to "b" if you have a 70cm DVAP - type = "dvap" - serial_number = "AP123456" // your serial number is visible through the case - frequency = 145.5 // this is the default value, chose a quiet frequency -# uncomment and set if you want the following to appear on you ircddb host website. -# range = 0.0 // in meters (1609.344 is one mile) -# agl = 0.0 // in meters -# latitude = 0.000000 // north is positive -# longitude = 0.000000 // east is positive -# desc1 = "Location1" // up to 20 chars -# desc2 = "location2" // up to 20 chars - } -} +module_c='dvap' # change to 'module_b' if you have a 70cm dvap +module_c_serial_number='AP123456' # your serial number is visible through the case +module_c_frequency=146.5 # in MHz, chose a quiet frequency -glink = { -# add the callsigns that can shutdown or reboot your system -# admin = [ "XX0XXX" , "YY0YYY" ] // only these users can execute scripts - -# link to the reflector of your choice. the first character is the module you are linking. -# link_at_start = "CREF001C" -} +#dplus_authorize=true # uncomment if you want to link to the legacy D-Plus system diff --git a/qn.everything.cfg b/qn.everything.cfg index e9dfa08..a838e5c 100644 --- a/qn.everything.cfg +++ b/qn.everything.cfg @@ -1,310 +1,239 @@ -# g2_ircddb Configuration - -ircddb = { - login = "CHANGEME!!!!"; # login callsign for the ircDDB network -# host = "rr.openquad.net" # other include group1-irc.ircddb.net -# port = 9007 # not a good idea to change! -# password = "1111111111111" # not needed for Openquad -} - -gateway = { -# regen_header = true # regenerate headers from incoming data -# send_qrgs_maps = true # send frequecy, offset, cooridinates and url to irc-server -# local_irc_ip = "0.0.0.0" # 0.0.0.0 means accept any incoming connections -# aprs_send = true # send info to aprs -# ip = "127.0.0.1" # where the gateway is running - - external = { -# ip = "0.0.0.0" -# port = 40000 - } - - internal = { -# ip = "0.0.0.0" -# port = 19000 - } -} - -module = { - a = { # An MMDVMHost module is shown - # make sure this module letter agrees with the [D-Star] Module letter in MMDVM.ini - # in MMDVM.ini its uppercase, here use lower case. - # We show a here, but the convention on this software is: - # 23 cm modules will use "a" - # 70 cm modules will use "b" - # 2 M module will use "c" - type = "mmdvm" -# ip = "127.0.0.1" -# port = 19998 # default for mod a, you usually don't need to specify this -# frequency = 0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage -# offset = 0 -# range = 0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles -# agl = 0 # the height above ground level for this repeater's antenna -# latitude = 0 # you can leave this unspecified for a mobile rig -# longitude = 0 # like the latitude -# desc1 = "" # maximum of 20 characters, most special symbols are not allowed -# desc2 = "" # just like desc1 -# url = "github.com/n7tae/g2_ircddb" # 80 characters max - # In review, for an MMDVM module, you only need to specify the type = "mmdvm" and - # make sure the module letter agrees with what's in "MMDVM.ini. - # Also see the mmdvm section below, although it usually doesn't need specifying. - } - - b = { - # a DVRPTR connected to a 70cm radio is shown in this example - - # type must be defined. This is how the dvrptr program finds the config params. - type = "dvrptr" - - # If you operate in "restriction mode", set callsign equal to your personal callsign - # Otherwise do not set callsign and we will use ircddb.username -# callsign = "" - - # the frequency of your DVRPTR in MHz. -# frequency = 145.5 - - # the TX/RX offset in MHz, use 0.0 for simplex -# offset = 0 - - # the range of this repeater, in meters 1609.344 meters is 1.0 miles -# range = 0.0 - - # the height above ground level for you repeater's antenna -# agl = 0.0 - - # the latitude of your repeater -# latitude = 0.0 - - # the longitude of your repeater -# longitude = 0.0 - - # This is used in a loop to wait for packets from your local gateway - # This is in milliseconds -# packet_wait = 25 - - # description of repeater, part one and two 20 char max each -# desc1 = "" -# desc2 = "" - - # the url of your repeater, 80 chars max, defaults to "github.com/n7tae/g2_ircddb" -# url = "" - - # If you want to enable/disable the repeater, set these options. - # Each of these options can NOT be more than 8 characters. - # Each of these options can NOT be another user's callsign. - # Each of these options can NOT be another repeater or reflector. - # Each of these options can NOT be a YRCALL command. - # If these options are set, then they can NOT be equal to each other. - # Using the above options, if you use YRCALL=RFISOFF in your radio - # then the repeater will be OFF and no audio will be copied over local RF - # and no audio will be accepted from any remote system. - # these command are disabled by default - # You can choose your own command strings, if you want to enable these - rf_control = { -# on = "RFISON" -# off = "RFISOFF" - } - - # To protect the repeater owners from bad STN programs out there - # and to also protect the repeater owners from RF users that abuse the STN stuff - # Reject values in YRCALL that start with STN - # If you want to allow the local RF users to subscribe to remote STN groups, - # then set it to XXX or something that is invalid like 123 -# invalid_prefix = "XXX" - - # Your DVRPTR V1 serial number - # If you don't know what it is, run the program and look in the log file! -# serial_number = "00.00.00.00" - -# internal_ip = "0.0.0.0" # the dvrptr address, usually leave this alone -# port = 19999 # module defaults: A=20000, B=19999, C=19998 - - # Some settings for your DVRPTR modem (see DVRPTR V1 manual for more info). -# rf_rx_level = 80 - - # If you need duplex, set it to true -# duplex = false - - # Do you want an ACK back ? -# acknowledge = false - - # ACK delay in milliseconds(ms) - # Minimum is 1, maximum is 999 - # If you do not get a repeater ACK, then make it a higher number - # Some radios get the ACK in 250ms, other radios require 750ms -# ack_delay = 300 - - # This is the TX-delay in milliseconds and it is used to delay the PTT. - # Seme radios have "SLOW" switching time, - # If your radio switches slow from RX to TX, then give your radio more time to switch, maybe 250 ms - # If your radio switches fast from RX to TX, then you could set it to 100 ms or maybe less - # But the best value should be the one to match your radio, so read the manual for your radio. - # In tests that were done for SLOW and FAST radios, we set it to 100 for FAST radios and 250 for SLOW radios. -# tx_delay = 250 - - # Dead firmware ? - # Lets say that you set RQST_COUNT=10 - # When there is NO local RF activity, (we do NOT receive anything from the DV-RPTR modem), - # then every 2 seconds we request the status from the DV-RPTR modem. - # If the DV-RPTR modem does NOT reply to our command, then after sending the command 10 times(RQST_COUNT) - # we have to assume the firmware in the DV-RPTR modem is DEAD. - # So, we send the command every 2 sedonds, and after sending the command 10 times, - # that is about (2 * 10) = 20 seconds, - # the repeater software will stop if the DV-RPTR modem does not respond after 20 seconds( 2 seconds * 10 times ) - # and then the service script will restart the repeater software. - # This is used to protect the repeater owner from BAD firmware. - # Minimum value is 6. - # If you see in the log this: "Modem is not responding... shuttting down" - # Then that means that the firmware died and the DV-RPTR modem stopped responding. - # You can increase the value of RQST_COUNT if you have a slow computer, - # (or maybe your computer is running too many programs and can not service the USB/serial fast enough) - # but we were informed that this is a bug in the firmware and they are trying to fix it. - # So, increasing the value for RQST_COUNT to higher than 10, does not make much sense. -# rqst_count = 10 - - # These values depend on what type of tranceiver is connected to your DV-RPTR modem - # Use either true or false - inverse = { -# rx = true -# tx = true - } - } - - c = { - # a 2m DVAP is shown as an example - - # type must be defined. This is how the dvap_rptr program finds the config params. - type = "dvap" - - # If you operate in "restriction mode", set RPTR equal to your personal callsign - # Otherwise do not set callsign and it will use ircddb.username -# callsign = "" - - # TX DVAP power level -12 to 10, 10 is maximum poower -# power = 10 - - # Squelch, -128 to -45, -100 to -80 usually works best -# squelch = -100 - - # To protect the repeater owners from bad STN programs out there - # and to also protect the repeater owners from RF users that abuse the STN stuff - # Reject values in YRCALL that start with STN - # If you want to allow the local RF users to subscribe to remote STN groups, - # then set it to XXX or something that is invalid like 123 -# ivalid_prefix = "XXX" - - # The serial number of you DVAP is visible through the bottom of the case -# serial_number = "APXXXXXX" - - # the DVAP will send a blip to acknowledge a transmission -# acknowledge = false - - # the frequency of your DVAP in MHz. -# frequency = 145.5 - - # this is for tweaking the frequency of your DVAP, see the owner's manual -# dvap_offset = 0 - - # the range of this repeater, in meters 1609.344 meters is 1.0 miles -# range = 0.0 - - # the height above ground level for you repeater's antenna -# agl = 0.0 - - # the latitude of your repeater -# latitude = 0.0 - - # the longitude of your repeater -# longitude = 0.0 - - # This is used in a loop to wait for packets from your local gateway - # This is in milliseconds -# packet_wait = 25 - - # description of repeater, part one and two 20 char max each -# desc1 = "" -# desc2 = "" - - # the url of your repeater, 80 chars max -# url = "github.com/n7tae/QnetGateway" - - # where other g2 programs find this repeater software -# ip = "127.0.0.1" # where is the device running? must be a "dotted number" - - # the internal ip of this program, "0.0.0.0" is usually best -# internal_ip = "0.0.0.0" - - # port number default: A:19998 B:19999 C:20000 -# port = 20000 # default for mod C - } -} - -# you only need this mmdvm section if you set non-standard D-Star ports on your MMDVMHost software -mmdvm = { -# these need to be the same as they are in your MMDVM.ini file (in the [D-Star Network] section -# If you change them there, then change them here! -# gateway_port = 20010 -# local_port = 20011 -} - -log = { - # debuging and extra logging switches -# qso = false # QSO info goes into the log -# irc = false # IRC debug info -# dtmf = false # DTMF debug info -} - -aprs = { # APRS.NET connect info -# host = "rotate.aprs.net" -# port = 14580 -# interval = 40 -# filter = "" -} - -link = { -# link_at_start = "NONE" # Link to a reflector at startup. -# to link repeater module B to REF001 C, use "BREF001C" -# ref_login = "" # for loging into REF reflectors, if undefined, ircddb.username will be used -# admin = [ "CALL1", "CALL2", "CALL3" ] # only these users can execute scripts, block dongles and reload the gwys.txt - # you probabaly want you own callsign in the admin list! -# link_unlink = [ "CALL4", "CALL5", "CALL6" ] # if defined, only these users can link and unlink a repeater -# no_link_unlink = [ "CALL7", "CALL8", "CALL9" ] # if defined, these users cannot link or unlink, it's a blacklist - # if the blacklist is defined (even if it's empty), the link_unlink will not be read -# incoming_ip = "0.0.0.0" # incoming ip address, "0.0.0.0" means accepts all connections. -# ip = "127.0.0.1" # where g2_link is running -# port = 18997 # port for communications to g2_link -# ref_port = 20001 # port for REF linking, don't change -# xrf_port = 30001 # port for XRF linking, don't change -# dcs_port = 30051 # port for DCS linking, don't change -# announce = true # do link, unlink, etc. announcements -# acknowledge = true # send text acknowledgement on key-up -# max_dongles = 5 # maximum number of linked hotspots -} - -file = { -# status = "/usr/local/etc/rptr_status" # where repeater status info is passed between services -# DTMF = "/tmp" # -# echotest = "/tmp" # echo dat files will end up here -# gwys = "/usr/local/etc/gwys.txt" # where the list of gateways and reflectors (with ports) is. -# announce_dir = "/usr/local/etc" # where are the *.dat files for the verbal link, unlink, etc. announcements -} - -timing = { - timeout = { -# echo = 1 # delay time in seconds for echo -# voicemail = 1 # delay time for voicemail -# remote_g2 = 2 # after this many seconds with no packets, we assume the tx is closed -# local_rptr = 1 # local timeout, in seconds - } - - play = { -# wait = 2 # seconds before playback occurs, between 1 and 10 -# delay = 19 # microseconds between frames playback, if echo sounds bad, adjust this up or down 1,2 microseconds - } - - inactivity = { -# a = 0 # unlink repeater if no activity for this many minutes -# b = 0 # zero mean there will be no timer -# c = 0 - } -} +# +# Copyright (c) 2019 by Thomas A. Early N7TAE +# +# 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 . + +######################################################################################################################### +# # +# qn.everything.cfg example configuration file # +# # +######################################################################################################################### +# What follows need to also be valid bash shell variable definitions, therefore: +# No white space on either side of the equal sign (=) +# String values should be quoted if they contain any special chars, including white space +# If a string value is a simple word, it doesn't need to be quoted +# Use the single quote (') for quoting strings, not the double quote(") +# Comments can come after a key=value definition, introduced by a pound-sign (#) +# +# if a definition is commented out, it means that key has that value as its default value. +# You don't need to uncomment it if that value is acceptable. In fact, you can remove the commented +# line altogether to simplify you qn.cfg file! In fact, you can remove all these comments and blank +# lines! + +########################################################################################################################## +# +# IRCDDB - You MUST use a legal callsign for logging into any IRC network +# +# you must specify you legal Callsign to enable QnetGateway +ircddb_login='' # login callsign for the ircDDB network +#ircddb_host='rr.openquad.net' # other irc networks include group1-irc.ircddb.net and group2-irc.ircddb.net +#ircddb_port=9007 # not a good idea to change! +#ircddb_password='1111111111111' # not needed for rr.openquad.net + +########################################################################################################################## +# +# GATEWAY +# +# Very few users will need to specify anything in the 'gateway' section! +#gateway_bool_regen_header=true # regenerate headers from incoming data +#gateway_send_qrgs_maps=true # send frequency, offset, coordinates and url to irc-server +#gateway_local_irc_ip='0.0.0.0' # the local port on the gateway for the IRC tcp socket +#gateway_external_ip='0.0.0.0' # this means accept a connection from any source +#gateway_external_port=40000 # don't change +#gateway_tolink='gate2link' # Unix sockets between qngateway and QnetLink +#gateway_fromlink='link2gate' # all Unix sockets are on the file system, but hidden from view + +########################################################################################################################## +# +# APRS - for tracking users and also this repeater. +# +#aprs_enable=true # uncomment and set this to 'false' if you don't wany the gateway interacting with APRS +#aprs_host='rotate.aprs.net' # the APRS network server +#aprs_port=14580 # and port +#aprs_interval=40 # keep-alive in minutes +#aprs_filter='' # advanced feature + +########################################################################################################################## +# +# LINK - controls the behaviour of QnetLink (qnlink) +# +#link_admin='' # these comma-separated list of users can execute scripts, block dongles, reload the gwys.txt + # if empty, everyone has admin privileges. +#link_link_unlink='' # if defined, comma-separated list of users that can link and unlink a repeater +#link_no_link_unlink='' # if defined, comma-separated list of users that cannot link or unlink, it's a blacklist. + # if a blacklist is defined and not empty, the link_unlink will not be read +#link_incoming_ip='0.0.0.0' # incoming ip address of qnlink, '0.0.0.0' means accepts any connection. +#link_ref_port=20001 # port for REF linking, don't change +#link_xrf_port=30001 # port for XRF linking, don't change +#link_dcs_port=30051 # port for DCS linking, don't change +#link_announce=true # do link, unlink, etc. announcements +#link_acknowledge=true # send text acknowledgment on key-up +#link_max_dongles=5 # maximum number of linked hot-spots + +########################################################################################################################## +# +# Here is an example MMDVM module on 70cm channel B +# +module_b='mmdvm' +#module_b_link_at_start='' # For example, set to 'REF001 C' to link module to reflector 1-charlie when the module starts. +#module_b_inactivity=0 # if no activity for this many minutes unlink any linked reflector. Zero means no timer. +#module_b_callsign='' # if you operate in a 'restriction mode', use your personal callsign. Usually leave this empty. +#module_b_packet_wait=25 # how many milliseconds to wait on packets in a voicestream +#module_b_acknowledge=false # Do you want an ACK back? +#module_b_ack_delay=250 # millisecond delay before acknowledgement +#module_b_frequency=0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage +#module_b_offset=0 # usually the duplex tx-rx offset, but for dvap, it's a frequency tweak +#module_b_range=0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles +#module_b_agl=0 # the height above ground level for this repeater's antenna +#module_b_latitude=0 # you can leave this unspecified for a mobile rig +#module_b_longitude=0 # like the latitude +#module_b_desc1='' # maximum of 20 characters, most special symbols are not allowed +#module_b_desc2='' # just like desc1 +#module_b_gate2modem1='gate2modem1' # Unix Sockets between a modem and the gateway +#module_b_modem2gate1='modem2gate1' # 0 is for A, 1 is for B and 2 is for C +#module_b_url='github.com/n7tae/g2_ircddb' # 80 characters max +# MMDVM - Special parameters when: module_b='mmdvm' +#module_b_internal_ip='0.0.0.0' # where MMDVMHost will find the QnetRelay program +#module_b_gateway_port=20010 # which port will QnetRelay be sending on +#module_b_local_port=20011 # which port will MMDVMHost be sending on + +########################################################################################################################## +# +# Here is an example ICOM Terminal and Access Point (ITAP) module specified on 2m channel C +# +module_c='itap' +#module_c_link_at_start='' # For example, set to 'REF001 C' to link module to reflector 1-charlie when the module starts. +#module_c_inactivity=0 # if no activity for this many minutes unlink any linked reflector. Zero means no timer. +#module_c_callsign='' # if you operate in a 'restriction mode', use your personal callsign. Usually leave this empty. +#module_c_packet_wait=25 # how many milliseconds to wait on packets in a voicestream +#module_c_acknowledge=false # Do you want an ACK back? +#module_c_ack_delay=250 # millisecond delay before acknowledgement +#module_c_frequency=0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage +#module_c_offset=0 # usually the duplex tx-rx offset, but for dvap, it's a frequency tweak +#module_c_range=0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles +#module_c_agl=0 # the height above ground level for this repeater's antenna +#module_c_latitude=0 # you can leave this unspecified for a mobile rig +#module_c_longitude=0 # like the latitude +#module_c_desc1='' # maximum of 20 characters, most special symbols are not allowed +#module_c_desc2='' # just like desc1 +#module_c_gate2modem1='gate2modem1' # Unix Sockets between a modem and the gateway +#module_c_modem2gate1='modem2gate1' # 0 is for A, 1 is for B and 2 is for C +#module_c_url='github.com/n7tae/g2_ircddb' # 80 characters max +# ITAP - Special parameters when: module_c='itap' +#module_c_device='/dev/ttyUSB0' # where the serial-to-USB cable shows up + +########################################################################################################################## +# +# DVAP - Here is an example 2M dvap +# +module_c='itap' +#module_c_link_at_start='' # For example, set to 'REF001 C' to link module to reflector 1-charlie when the module starts. +#module_c_inactivity=0 # if no activity for this many minutes unlink any linked reflector. Zero means no timer. +#module_c_callsign='' # if you operate in a 'restriction mode', use your personal callsign. Usually leave this empty. +#module_c_packet_wait=25 # how many milliseconds to wait on packets in a voicestream +#module_c_acknowledge=false # Do you want an ACK back? +#module_c_ack_delay=250 # millisecond delay before acknowledgement +#module_c_frequency=0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage +#module_c_offset=0 # usually the duplex tx-rx offset, but for dvap, it's a frequency tweak +#module_c_range=0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles +#module_c_agl=0 # the height above ground level for this repeater's antenna +#module_c_latitude=0 # you can leave this unspecified for a mobile rig +#module_c_longitude=0 # like the latitude +#module_c_desc1='' # maximum of 20 characters, most special symbols are not allowed +#module_c_desc2='' # just like desc1 +#module_c_gate2modem1='gate2modem1' # Unix Sockets between a modem and the gateway +#module_c_modem2gate1='modem2gate1' # 0 is for A, 1 is for B and 2 is for C +#module_c_url='github.com/n7tae/g2_ircddb' # 80 characters max +# DVAP - Special parameters when: module_c='dvap' +#module_c_power=10 # TX power level: -12 to 10, 10 is maximum power +#module_c_squelch=-100 # RX Squelch: -128 to -45, -100 to -80 usually works best +#module_c_serial_number='APXXXXXX' # The serial number of your DVAP is visible through the bottom of the case + +########################################################################################################################## +# +# DVRPTR - Here is an example 70cm dvrptr +# +module_b='dvrptr' +#module_b_link_at_start='' # For example, set to 'REF001 C' to link module to reflector 1-charlie when the module starts. +#module_b_inactivity=0 # if no activity for this many minutes unlink any linked reflector. Zero means no timer. +#module_b_callsign='' # if you operate in a 'restriction mode', use your personal callsign. Usually leave this empty. +#module_b_packet_wait=25 # how many milliseconds to wait on packets in a voicestream +#module_b_acknowledge=false # Do you want an ACK back? +#module_b_ack_delay=250 # millisecond delay before acknowledgement +#module_b_frequency=0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage +#module_b_offset=0 # usually the duplex tx-rx offset, but for dvap, it's a frequency tweak +#module_b_range=0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles +#module_b_agl=0 # the height above ground level for this repeater's antenna +#module_b_latitude=0 # you can leave this unspecified for a mobile rig +#module_b_longitude=0 # like the latitude +#module_b_desc1='' # maximum of 20 characters, most special symbols are not allowed +#module_b_desc2='' # just like desc1 +#module_b_gate2modem1='gate2modem1' # Unix Sockets between a modem and the gateway +#module_b_modem2gate1='modem2gate1' # 0 is for A, 1 is for B and 2 is for C +#module_b_url='github.com/n7tae/g2_ircddb' # 80 characters max +# DVRPTR - Special parameters when: module_b='dvrptr' +# if you don't know what your DVRPTR serial number is, look in the log file after running qndvrptr +#module_b_serial_number='00.00.00.00' # the DVRPTR serial number +#module_b_rf_on='RFON' # put this in YRCALL to disable the channel +#module_b_rf_off='RFOFF' # put this in YRCALL to enable the channel +#module_b_rf_rx_level=80 # see the DVRPTR V1 manual +#module_b_duplex=false # set to true if the module is duplex +#module_b_tx_delay=250 # milliseconds to allow for switching from rx to tx +#module_b_rqst_count=10 # number of 2-sec intervals before the an unresponsive system is killed +#module_b_inverse_rx=true # if you're not hearing anything, try false +#module_b_inverse_tx=true # if you're not being heard, try false + +########################################################################################################################## +# +# LOGGING - Control extra logging - useful for debugging +# +#log_qso=false # QSO info goes into the log +#log_irc=false # IRC debug info +#log_dtmf=false # DTMF debug info + +########################################################################################################################## +# +# DPLUS - Control of dplus (trust system) linking to repeaters and REF reflectors +# +# The following settings do not affect your ability to use dplus linking to XRF or XLX reflectors! +# You must be registered on the DPlus system, see www.dstargateway.org, otherwise authorization will fail, +# even if QnetLink reports a successful authorization. +#dplus_authorize=false # to enable, uncomment and set to true to link to DPlus reflectors and/or repeaters +#dplus_ref_login='' # for logging into REF reflectors, if empty, ircddb_login will be used +#dplus_use_reflectors=true # set to false if you are not going to link to DPlus reflectors +#dplus_use_repeaters=true # set to false if you are not going to link to DPlus repeaters +# any gateways in your gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. + +########################################################################################################################## +# +# FILE - where important QnetGateway files and directories are found. +# +#file_status='/usr/local/etc/rptr_status' # where repeater status info is passed between services +#file_dtmf='/tmp' # where DTMF is decoded +#file_echotest='/tmp' # echo dat files will end up here +#file_qnvoicefile='/tmp/qnvoice.txt' # where qnvoice will create the play command +#file_gwys='/usr/local/etc/gwys.txt' # where the list of gateways and reflectors (with ports) is. +#file_announce_dir='/usr/local/etc' # where the *.dat files are for the verbal link, unlink, etc. announcements + +########################################################################################################################## +# +# TIMINGS - for controlling how to deal with timing issues +# +# most users will not have to override any of these default values +#timing_timeout_echo=1 # seconds before we assume echo has timed out +#timing_timeout_voicemail=1 # seconds before we assume voicemail has timed out +#timing_timeout_remote_g2=2 # after this many seconds with no packets, we assume the tx is closed +#timing_timeout_local_rptr=1 # local repeater timeout, in seconds +#timing_play_wait=1 # seconds before echo or voicemail playback occurs, between 1 and 10 +#timing_play_delay=19 # microseconds between frames playback, if echo sounds bad, adjust this up or down 1 or 2 ms diff --git a/qn.itap.cfg b/qn.itap.cfg new file mode 100644 index 0000000..04c464f --- /dev/null +++ b/qn.itap.cfg @@ -0,0 +1,7 @@ +# A Simple Configuration File for an ICOM using Terminal and Access Point Mode + +ircddb_login='Q1ABC' + +module_c='itap' + +#dplus_authorize=true # uncomment if you want to use the closed-source DPlus reflectors and/or repeaters diff --git a/qn.mmdvm.cfg b/qn.mmdvm.cfg index 36bf9cc..c2d3e6d 100644 --- a/qn.mmdvm.cfg +++ b/qn.mmdvm.cfg @@ -3,41 +3,16 @@ # Please see qn.everything.cfg for many configurable items. # New-bees beware, it is possible to configure your system # to a non-functional state. Nearly all configure items -# already have good default vaules, but the few below +# already have good default values, but the two below # HAVE TO BE SET BY YOU!!! -ircddb = { +ircddb_login='Q1ABC' -# Use your callsign, the default network is QuadNet2 - login = "YOUR CALLSIGN" +module_x='mmdvm' -} +# Change the "x" to the lowercase equivilent of the module assignment in you MMDVM.qn initialization file. +# Use B in the .ini file and b in this file for 70cm and C and c for 2M. See the qn.everything.cfg file if you want to include +# location data for your repeater/hot-spot. (Location data in your MMDVM.qn ini file will +# not make it to the D-Star network.) -module = { - -# Change the "x" to the lowercase equivilent of the module -# assignment in you MMDVM.qn initialization file. -# Use B and b for 70cm and C and c for 2M. - - x = { - - type = "mmdvm" - -# See the qn.everything.cfg file if you want to include -# location data for your repeater/hot-spot. -# (Location data in your MMDVM.qn ini file will -# not make it to the D-Star network.) - - } -} - -link = { - -# Only callsigns listed in the admin list can execute -# Linux scripts from you radio. Two are already provided: -# _ _ _ _ _ _ R X reboots your system, and -# _ _ _ _ _ _ H X halts it. - - admin = [ "AA0AAA" , "BB1BBB" , "CC3CCC" ] - -} +#dplus_authorize='true' # uncomment if you want to use the closed-source DPlus reflectors and/or repeaters diff --git a/qnadmin b/qnadmin new file mode 100755 index 0000000..5f17450 --- /dev/null +++ b/qnadmin @@ -0,0 +1,654 @@ +#!/bin/bash +# +# Copyright (c) 2019 by Thomas A. Early N7TAE +# +# 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 . + +trap ControlC INT + +ControlC () { + echo "caught SIGINT" +} + +InstallMMDVMHosts () { + if [ $nmmdvm -gt 0 ] && [ -e ${MMPATH}/MMDVMHost ]; then + if [ $nmmdvm -eq 1 ]; then + sudo make ${1}installmmdvm + else + sudo make MODULE=${ammdvm[0]} ${1}installmmdvm + sudo make MODULE=${ammdvm[1]} ${1}installmmdvm + if [ $nmmdvm -eq 3 ]; then + sudo make MODULE=${advap[2]} ${1}installmmdvm + fi + fi + fi +} + +InstallSystem () { + local n + if [ -z ${1} ]; then + n=$( grep '^processor' /proc/cpuinfo | wc -l ) + echo "Detected $n processors for make" + make base -j$n + fi + sudo make ${1}installbase + if [ $ndvap -gt 0 ]; then + if [ -z ${1} ]; then + make qndvap -j$n + fi + if [ $ndvap -eq 1 ]; then + sudo make ${1}installdvap + else + sudo make MODULE=${advap[0]} ${1}installdvap + sudo make MODULE=${advap[1]} ${1}installdvap + if [ $ndvap -eq 3 ]; then + sudo make MODULE=${advap[2]} ${1}installdvap + fi + fi + fi + if [ $ndvrptr -gt 0 ]; then + if [ -z ${1} ]; then + make qndvrptr -j$n + fi + if [ $ndvrptr -eq 1 ]; then + sudo make ${1}installdvrptr + else + sudo make MODULE=${advrptr[0]} ${1}installdvrptr + sudo make MODULE=${advrptr[1]} ${1}installdvrptr + if [ $ndvrptr -eq 3 ]; then + sudo make MODULE=${advrptr[2]} ${1}installdvrptr + fi + fi + fi + if [ $nitap -gt 0 ]; then + if [ -z ${1} ]; then + make qnitap -j$n + fi + if [ $nitap -eq 1 ]; then + sudo make ${1}installitap + else + sudo make MODULE=${aitap[0]} ${1}installitap + sudo make MODULE=${aitap[1]} ${1}installitap + if [ $nitap -eq 3 ]; then + sudo make MODULE=${aitap[2]} ${1}installitap + fi + fi + fi + if [ $nmmdvm -gt 0 ]; then + if [ -z ${1} ]; then + make qnrelay -j$n + fi + if [ $nmmdvm -eq 1 ]; then + sudo make ${1}installrelay + else + sudo make MODULE=${ammdvm[0]} ${1}installrelay + sudo make MODULE=${ammdvm[1]} ${1}installrelay + if [ $nmmdvm -eq 3 ]; then + sudo make MODULE=${advap[2]} ${1}installrelay + fi + fi + fi + if [ $nmodem -gt 0 ]; then + if [ -z ${1} ]; then + make qnmodem -j$n + fi + if [ $nmodem -eq 1 ]; then + sudo make ${1}installmodem + else + sudo make MODULE=${amodem[0]} ${1}installmodem + sudo make MODULE=${amodem[1]} ${1}installmodem + if [ $nmmdvm -eq 3 ]; then + sudo make MODULE=${amodem[2]} ${1}installmodem + fi + fi + fi +} + +BaseStatus () { + local LoadGate SubGate LoadLink SubLink LoadDTMF SubDTMF + LoadGate=$( systemctl show -p LoadState --value qngateway ) + SubGate=$( systemctl show -p SubState --value qngateway ) + LoadLink=$( systemctl show -p LoadState --value qnlink ) + SubLink=$( systemctl show -p SubState --value qnlink ) + LoadDTMF=$( systemctl show -p LoadState --value qndtmf ) + SubDTMF=$( systemctl show -p SubState --value qndtmf ) + if [[ $LoadGate == 'loaded' ]]; then + if [[ $SubGate == 'running' ]]; then + GateState='running' + else + GateState='stopped' + fi + else + GateState='not installed' + fi + echo "QnetGateway is $GateState" + if [[ $LoadLink == 'loaded' ]]; then + if [[ $SubLink == 'running' ]]; then + LinkState='running' + else + LinkState='stopped' + fi + else + LinkState='not installed' + fi + echo "QnetLink is $LinkState" + if [[ $LoadDTMF == 'loaded' ]]; then + if [[ $SubDTMF == 'running' ]]; then + DTMFState='running' + else + DTMFState='stopped' + fi + else + DTMFState='not installed' + fi + echo "DTMF is $DTMFState" +} + +ModuleStatus () { + local LoadState SubState mcvar + if [ -z ${3} ]; then + ModuleState[$1]='EMPTY' + MMDVMState[$1]='EMPTY' + else + mcvar="n${3}" + ModuleProcess[$1]="qn$3" + if [[ "${ModuleProcess[$1]}" == 'qnmmdvmhost' ]]; then + ModuleProcess[$1]='qnrelay' + MMDVMProcess[$1]='mmdvm' + MMDVMState[$1]='not installed' + elif [[ "${ModuleProcess[$1]}" == 'qnmmdvmmodem' ]]; then + ModuleProcess[$1]='qnmodem' + MMDVMProcess[$1]='' + MMDVMState[$1]='EMPTY' + else + MMDVMState[$1]='EMPTY' + MMDVMProcess[$1]='' + fi + if [[ ${!mcvar} > 1 ]]; then + ModuleProcess[$1]="${ModuleProcess[$1]}${2}" + if [[ "${MMDVMProcess[$1]}" == 'mmdvm' ]]; then + MMDVMProcess[$1]="${MMDVMProcess[$1]}${2}" + fi + fi + LoadState=$( systemctl show -p LoadState --value ${ModuleProcess[$1]} ) + SubState=$( systemctl show -p SubState --value ${ModuleProcess[$1]} ) + if [[ "$LoadState" == "loaded" ]]; then + if [[ $SubState == "running" ]]; then + ModuleState[$1]='running' + else + ModuleState[$1]='stopped' + fi + else + ModuleState[$1]='not installed' + fi + if [[ "${MMDVMState[$1]}" != 'EMPTY' ]]; then + LoadState=$( systemctl show -p LoadState --value ${MMDVMProcess[$1]} ) + SubState=$( systemctl show -p SubState --value ${MMDVMProcess[$1]} ) + if [[ "$LoadState" == "loaded" ]]; then + if [[ $SubState == "running" ]]; then + MMDVMState[$1]='running' + else + MMDVMState[$1]='stopped' + fi + else + MMDVMState[$1]='not installed' + fi + fi + if [[ "$3" == 'mmdvmhost' ]]; then + echo "Module ${2^^} - ${ModuleProcess[$1]} is ${ModuleState[$1]} - ${MMDVMProcess[$1]} is ${MMDVMState[$1]}" + else + echo "Module ${2^^} - ${ModuleProcess[$1]} is ${ModuleState[$1]}" + fi + fi +} + +Header () { + local count + count=$( ps -aux | grep -e qn -e MMDVMHost | wc -l ) + if [ ${count} -gt 3 ]; then + echo + echo "USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND" + ps -aux | grep -e qngateway -e qnlink -e qndtmf -e qndvap -e qnitap -e qnrelay -e qndvrptr -e qnmodem -e MMDVMHost | grep -v grep + fi + echo +} + +LogMenu () { + ans='' + while [[ "$ans" != q* ]]; do + clear + echo " Log Menu" + Header + echo "After starting a log, use +C to stop the log and return to this menu." + echo + if [[ $GateState == 'running' ]]; then + echo " g : qngateway Log" + else + echo " qngateway is not running" + fi + if [[ $LinkState == 'running' ]]; then + echo " l : qnlink Log" + else + echo " qnlink is not running" + fi + if [[ $DTMFState == 'running' ]]; then + echo " d : qndtmf Log" + else + echo " qndtmf is not running" + fi + if [[ ${ModuleState[0]} != 'EMPTY' ]]; then + if [[ ${ModuleState[0]} == 'running' ]]; then + echo " a : ${ModuleProcess[0]} Log" + else + echo " ${ModuleProcess[0]} is not running" + fi + fi + if [[ ${ModuleState[1]} != 'EMPTY' ]]; then + if [[ ${ModuleState[1]} == 'running' ]]; then + echo " b : ${ModuleProcess[1]} Log" + else + echo " ${ModuleProcess[1]} is not running" + fi + fi + if [[ ${ModuleState[2]} != 'EMPTY' ]]; then + if [[ ${ModuleState[2]} == 'running' ]]; then + echo " c : ${ModuleProcess[2]} Log" + else + echo " ${ModuleProcess[2]} is not running" + fi + fi + if [[ ${MMDVMState[0]} != 'EMPTY' ]]; then + if [[ ${MMDVMState[0]} == 'running' ]]; then + echo " x : ${MMDVMProcess[0]} Log" + else + echo " ${MMDVMProcess[0]} is not running" + fi + fi + if [[ ${MMDVMState[1]} != 'EMPTY' ]]; then + if [[ ${MMDVMState[1]} == 'running' ]]; then + echo " y : ${MMDVMProcess[1]} Log" + else + echo " ${MMDVMProcess[1]} is not running" + fi + fi + if [[ ${MMDVMState[2]} != 'EMPTY' ]]; then + if [[ ${MMDVMState[2]} == 'running' ]]; then + echo " z : ${MDVMProcess[2]} Log" + else + echo " ${MMDVMProcess[2]} is not running" + fi + fi + echo + echo " q : quit and return to main menu" + echo + read -p "Command: " ans + + if [[ "$ans" == g* ]] && [[ "$GateState" == 'running' ]]; then + sudo journalctl -u qngateway -f + elif [[ "$ans" == l* ]] && [[ "$LinkState" == 'running' ]]; then + sudo journalctl -u qnlink -f + elif [[ "$ans" == d* ]] && [[ "$DTMFState" == 'running' ]]; then + sudo journalctl -u qndtmf -f + elif [[ "$ans" == a* ]] && [[ "${ModuleState[0]}" == 'running' ]]; then + sudo journalctl -u ${ModuleProcess[0]} -f + elif [[ "$ans" == b* ]] && [[ "${ModuleState[1]}" == 'running' ]]; then + sudo journalctl -u ${ModuleProcess[1]} -f + elif [[ "$ans" == c* ]] && [[ "${ModuleState[2]}" == 'running' ]]; then + sudo journalctl -u ${ModuleProcess[2]} -f + elif [[ "$ans" == x* ]] && [[ "${MMDVMState[0]}" == 'running' ]]; then + sudo journalctl -u ${MMDVMProcess[0]} -f + elif [[ "$ans" == y* ]] && [[ "${MMDVMState[1]}" == 'running' ]]; then + sudo journalctl -u ${MMDVMProcess[1]} -f + elif [[ "$ans" == z* ]] && [[ "${MMDVMState[2]}" == 'running' ]]; then + sudo journalctl -u ${MMDVMProcess[2]} -f + fi + done + ans='' +} + +MaintenanceMenu () { + local m n + ans='' + while [[ "$ans" != q* ]]; do + clear + echo " MaintenanceMenu" + Header + if [[ "$GateState" == 'running' ]]; then + echo " g : Stop QnetGateway" + elif [[ "$GateState" == 'stopped' ]]; then + echo " g : Start QnetGateway" + fi + if [[ "$LinkState" == 'running' ]]; then + echo " l : Stop QnetLink" + elif [[ "$LinkState" == 'stopped' ]]; then + echo " l : Start QnetLink" + fi + if [[ "$DTMFState" == 'running' ]]; then + echo " d : Stop DTMF" + elif [[ "$DTMFState" == 'stopped' ]]; then + echo " d : Start DTMF" + fi + if [[ "${ModuleState[0]}" == 'running' ]]; then + echo " a : Stop Module A" + elif [[ "${ModuleState[0]}" == 'stopped' ]]; then + echo " a : Start Module A" + fi + if [[ "${ModuleState[1]}" == 'running' ]]; then + echo " b : Stop Module B" + elif [[ "${ModuleState[1]}" == 'stopped' ]]; then + echo " b : Start Module B" + fi + if [[ "${ModuleState[2]}" == 'running' ]]; then + echo " c : Stop Module C" + elif [[ "${ModuleState[2]}" == 'stopped' ]]; then + echo " c : Start Module C" + fi + if [[ "${MMDVMState[0]}" == 'running' ]]; then + echo " x : Stop MMDVMHost A" + elif [[ "${MMDVMState[0]}" == 'stopped' ]]; then + echo " x : Start MMDVMHost A" + fi + if [[ "${MMDVMState[1]}" == 'running' ]]; then + echo " y : Stop MMDVMHost B" + elif [[ "${MMDVMState[1]}" == 'stopped' ]]; then + echo " y : Start MMDVMHost B" + fi + if [[ "${MMDVMState[2]}" == 'running' ]]; then + echo " z : Stop MMDVMHost C" + elif [[ "${MMDVMState[2]}" == 'stopped' ]]; then + echo " z : Start MMDVMHost C" + fi + echo + echo " q : Return to main Menu" + echo + read -p "Command: " ans + + if [[ "$ans" == g* ]]; then + if [[ "$GateState" == 'running' ]]; then + sudo systemctl stop qngateway + elif [[ "$GateState" == 'stopped' ]]; then + sudo systemctl start qngateway + fi + BaseStatus + elif [[ "$ans" == l* ]]; then + if [[ "$LinkState" == 'running' ]]; then + sudo systemctl stop qnlink + elif [[ "$LinkState" == 'stopped' ]]; then + sudo systemctl start qnlink + fi + BaseStatus + elif [[ "$ans" == d* ]]; then + if [[ "$DTMFState" == 'running' ]]; then + sudo systemctl stop qndtmf + elif [[ "$DTMFState" == 'stopped' ]]; then + sudo systemctl start qndtmf + fi + BaseStatus + elif [[ "$ans" == a* ]] && [ -n $module_a ]; then + if [[ "${ModuleState[0]}" == 'running' ]]; then + sudo systemctl stop ${ModuleProcess[0]} + elif [[ "${ModuleState[0]}" == 'stopped' ]]; then + sudo systemctl start ${ModuleProcess[0]} + fi + ModuleStatus 0 a $module_a + elif [[ "$ans" == b* ]] && [ -n $module_b ]; then + if [[ "${ModuleState[1]}" == 'running' ]]; then + sudo systemctl stop ${ModuleProcess[1]} + elif [[ "${ModuleState[1]}" == 'stopped' ]]; then + sudo systemctl start ${ModuleProcess[1]} + fi + ModuleStatus 1 b $module_b + elif [[ "$ans" == c* ]] && [ -n $module_c ]; then + if [[ "${ModuleState[2]}" == 'running' ]]; then + sudo systemctl stop ${ModuleProcess[2]} + elif [[ "${ModuleState[2]}" == 'stopped' ]]; then + sudo systemctl start ${ModuleProcess[2]} + fi + ModuleStatus 2 c $module_c + elif [[ "$ans" == x* ]] && [ -n $module_a ]; then + if [[ "${MMDVMState[0]}" == 'running' ]]; then + sudo systemctl stop ${MMDVMProcess[0]} + elif [[ "${MMDVMState[0]}" == 'stopped' ]]; then + sudo systemctl start ${MMDVMProcess[0]} + fi + ModuleStatus 0 a $module_a + elif [[ "$ans" == y* ]] && [ -n $module_b ]; then + if [[ "${MMDVMState[1]}" == 'running' ]]; then + sudo systemctl stop ${MMDVMProcess[1]} + elif [[ "${MMDVMState[1]}" == 'stopped' ]]; then + sudo systemctl start ${MMDVMProcess[1]} + fi + ModuleStatus 1 b $module_b + elif [[ "$ans" == z* ]] && [ -n $module_c ]; then + if [[ "${MMDVMState[2]}" == 'running' ]]; then + sudo systemctl stop ${MMDVMProcess[2]} + elif [[ "${MMDVMState[2]}" == 'stopped' ]]; then + sudo systemctl start ${MMDVMProcess[2]} + fi + ModuleStatus 2 c $module_c + fi + done + ans='' +} + +GatewayMenu () { + ans='' + while [[ "$ans" != q* ]]; do + local refcount=$( grep -s "^REF" gwys.txt | wc -l ) + local dcscount=$( grep -s "^DCS" gwys.txt | wc -l ) + local xrfcount=$( grep -s "^XRF" gwys.txt | wc -l ) + local repcount=$( grep -s -v "^#" gwys.txt | grep -s -v -e "^REF" -e "^DCS" -e "^XRF" | wc -l ) + clear + echo + echo " Gateway File" + echo + echo " $refcount REF, $dcscount DCS, $xrfcount XRF and $repcount Repeaters" + echo + echo " All initialization will overwrite any existing file!" + echo "ip : Initialize gwys.txt using www.PiStar.uk/downloads and xlxapi.rlx.lu" + echo "iv : Initialize gwys.txt using VA3UV.com (includes a large # of repeaters)" + echo "ir : Initialize gwys.txt using VA3UV.com (but just the reflectors)" + echo "n : Edit gwys.txt file with nano" + echo "v : Edit gwys.txt file with vi" + echo + echo "q : Quit and return to Main Menu" + echo + read -p "Command: " ans + if [[ "$ans" == ip* ]]; then + wget -O XLX_Hosts.txt http://xlxapi.rlx.lu/api.php?do=GetXLXDMRMaster + wget http://www.pistar.uk/downloads/DExtra_Hosts.txt + wget http://www.pistar.uk/downloads/DPlus_Hosts.txt + wget http://www.pistar.uk/downloads/DCS_Hosts.txt + /bin/rm -f gwys.txt + echo "# Downloaded from www.pistar.uk and xlxapi.rlx.lu `date`" > gwys.txt + awk '$1 ~ /^XLX/ { gsub("\r", ""); printf "%s %s 30001\n", $1, $2 }' XLX_Hosts.txt >> gwys.txt + awk '$1 ~ /^XRF/ { printf "%s %s 30001\n", $1, $2 }' DExtra_Hosts.txt >> gwys.txt + awk '$1 ~ /^DCS/ { printf "%s %s 30051\n", $1, $2 }' DCS_Hosts.txt >> gwys.txt + awk '$1 ~ /^REF/ { printf "%s %s 20001\n", $1, $2 }' DPlus_Hosts.txt >> gwys.txt + /bin/rm -f {XLX,DExtra,DPlus,DCS}_Hosts.txt + elif [[ "$ans" == iv* ]]; then + /bin/rm -f gwys.txt + wget http://www.va3uv.com/gwys.txt + elif [[ "$ans" == ir* ]]; then + /bin/rm -f gwys.txt + wget -nv -O gwys.va3uv.txt http://www.va3uv.com/gwys.txt + if [ -e gwys.va3uv.txt ]; then + echo "# Downloaded from www.va3uv.com `date`" > gwys.txt + awk '$1~/^REF/{print $1, $2, $3}' gwys.va3uv.txt >> gwys.txt + awk '$1~/^XRF/{print $1, $2, $3}' gwys.va3uv.txt >> gwys.txt + awk '$1~/^DCS/{print $1, $2, $3}' gwys.va3uv.txt >> gwys.txt + rm -f gwys.va3uv.txt + fi + elif [[ "$ans" == n* ]]; then + nano gwys.txt + elif [[ "$ans" == v* ]]; then + vi gwys.txt + fi + done + ans='' +} + +# get defined modules from the config file +if [ -e qn.cfg ]; then + source <( grep "^module_[abc]=" qn.cfg ) + if [ -z "$module_a" ] && [ -z "$module_b" ] && [ -z "$module_c" ]; then + echo "No moudules defined in the qn.cfg file!, please re-edit you configuration." + exit 1 + fi +else + echo "ERROR: can't find the qn.cfg configuration file!" + echo "You can use ./qnconfig to create your config file." + exit 1 +fi + +# get the installation directory from the make file +if [ -e makefile ]; then + MAKEFILE=makefile +elif [ -e Makefile ]; then + MAKEFILE=Makefile +else + echo "ERROR: There is no Makefile or makefile" + exit 1 +fi +source <( grep -e '^BINDIR=' -e '^MMPATH=' $MAKEFILE ) +if [ -z $BINDIR ]; then + echo "ERROR: The BINDIR definition in $MAKEFILE is empty!" + exit 1 +fi +if [ ! -d "$BINDIR" ]; then + echo "ERROR: The BINDIR directory $BINDIR is not a directory!" + exit 1 +fi + +ndvap=0 +ndvrptr=0 +nitap=0 +nmmdvm=0 +nmodem=0 + +for m in a b c ; do + mod=module_${m} + if [ -z ${!mod} ]; then continue; fi + type=${!mod} + if [[ "$type" == 'dvap' ]]; then + advap[${ndvap}]=${m} + ndvap=$((ndvap + 1)) + elif [[ "$type" == 'dvrptr' ]]; then + advrptr[$ndvap]=${m} + ndvrptr=$((ndvrptr + 1)) + elif [[ "$type" == 'itap' ]]; then + aitap[${nitap}]=${m} + nitap=$((nitap + 1)) + elif [[ "$type" == 'mmdvmhost' ]]; then + ammdvm[${nmmdvm}]=${m} + nmmdvm=$((nmmdvm + 1)) + elif [[ "$type" == 'mmdvmmodem' ]]; then + amodem[${nmodem}]=${m} + nmodem=$((nmodem + 1)) + fi +done + +MODULE_COUNT=$((ndvap + ndvrptr + nitap + nmmdvm + nmodem)) + +while [[ "$ans" != q* ]]; do + clear + echo + echo " Qnet Administration Menu" + Header + BaseStatus + ModuleStatus 0 a "$module_a" + ModuleStatus 1 b "$module_b" + ModuleStatus 2 c "$module_c" + echo + if [[ "$GateState" == 'not installed' ]] || [[ "$LinkState" == 'not installed' ]]; then + if [ -e gwys.txt ]; then + echo "is : Install configured system" + echo "gp : Do a 'git pull' to refresh sources" + else + echo " You must set-up your gwys.txt file before installation." + fi + else + echo "us : Uninstall configured System" + fi + if [ -e "${MMPATH}/MMDVMHost" ] && [ $nmmdvm -gt 0 ]; then + if [[ "${MMDVMState[0]}" != 'EMPTY' ]] || [[ "${MMDVMState[1]}" != 'EMPTY' ]] || [[ "${MMDVMState[2]}" != 'EMPTY' ]]; then + if [[ "${MMDVMState[0]}" == 'not installed' ]] || [[ "${MMDVMState[1]}" == 'not installed' ]] || [[ "${MMDVMState[2]}" == 'not installed' ]]; then + echo "im : Install MMDVMHost(s)" + else + echo "um : Uninstall MMDVMHost(s)" + fi + fi + fi + echo "gw : Gateway File Menu" + echo + if [[ "$DTMFState" == 'not installed' ]]; then + echo "nd : Edit DTMF file with nano" + echo "vd : Edit DTMF file with vi" + if [ -e qndtmf ] && [ -n "`diff -q qndtmf qndtmf.sh`" ]; then + echo "rd : Restore DTMF file to default (this will overwrite existing DTMF file)" + fi + echo "id : Install DTMF" + else + echo "ud : Uninstall DTMF" + fi + echo + echo "c : Clean (remove temporary files and locally built executables)" + echo + if [[ "$GateState" != 'not installed' ]] && [[ "$LinkState" != 'not installed' ]]; then + echo "m : Maintenance Menu" + echo "l : Log Menu" + fi + echo + read -p "q to quit. Command: " ans + + # EXECUTE COMMANDS + if [[ "$GateState" == 'not installed' ]] && [[ "$ans" == is* ]]; then + InstallSystem + echo 'Wait for a few seconds for everything to start...' + sleep 5 + elif [[ "$GateState" != 'not installed' ]] && [[ "$ans" == us* ]]; then + InstallSystem un + elif { [[ "${MMDVMState[0]}" == 'not installed' ]] || [[ "${MMDVMState[1]}" == 'not installed' ]] || [[ "${MMDVMState[2]}" == 'not installed' ]]; } && [ -e ${MMPATH}/MMDVMHost ] && [[ "$ans" == im* ]]; then + InstallMMDVMHosts + elif { [[ "${MMDVMState[0]}" == 'stopped' ]] || [[ "${MMDVMState[0]}" == 'running' ]] || [[ "${MMDVMState[1]}" == 'stopped' ]] || [[ "${MMDVMState[1]}" == 'running' ]] || [[ "${MMDVMState[2]}" == 'stopped' ]] || [[ "${MMDVMState[2]}" == 'running' ]]; } && [ -e ${MMPATH}/MMDVMHost ] && [[ "$ans" == um* ]]; then + InstallMMDVMHosts un + elif [[ "$GateState" == 'not installed' ]] && [[ "$ans" == gp* ]]; then + git pull + read -p 'Press to continue: ' ans + ans='' + elif [[ "$ans" == gw* ]]; then + GatewayMenu + elif [[ "$DTMFState" == 'not installed' ]] && [[ "$ans" == nd* ]]; then + if [ ! -e qndtmf ]; then cp qndtmf.sh qndtmf; fi + nano qndtmf + elif [[ "$DTMFState" == 'not installed' ]] && [[ "$ans" == vd* ]]; then + if [ ! -e qndtmf ]; then cp qndtmf.sh qndtmf; fi + vi qndtmf + elif [[ "$DTMFState" == 'not installed' ]] && [ -e qndtmf ] && [ -n "`diff -q qndtmf qndtmf.sh`" ] && [[ "$ans" == rd* ]]; then + rm -f qndtmf + elif [[ "$DTMFState" == 'not installed' ]] && [[ "$ans" == id* ]]; then + if [ ! -e qndtmf ]; then cp qndtmf.sh qndtmf; fi + sudo make installdtmf + elif [[ "$DTMFState" != 'not installed' ]] && [[ "$ans" == ud* ]]; then + sudo make uninstalldtmf + elif [[ "$ans" == c* ]]; then + make clean + elif [[ "$ans" == m* ]]; then + MaintenanceMenu + elif [[ "$ans" == l* ]]; then + LogMenu + fi +done + +exit 0 diff --git a/qnconfig b/qnconfig new file mode 100755 index 0000000..4e26776 --- /dev/null +++ b/qnconfig @@ -0,0 +1,717 @@ +#!/bin/bash +# +# Copyright (c) 2019-2020 by Thomas A. Early N7TAE +# +# 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 . + +SetBooleanValue () { + local dvname + local cv + if [ -z $2 ]; then + if [ -z ${!1+x} ]; then + if [[ "$1" == module_[abc]_* ]]; then + echo matches + dvname=${1//_[abc]_/_x_} + else + echo does not match + dvname=${1}_d + fi + cv=${!dvname} + else + cv=${!1} + fi + if [[ $cv == [tT]* ]]; then + eval ${1}=false + else + eval ${1}=true + fi + elif [[ "$2" == [tT]* ]]; then + eval ${1}=true + else + eval ${1}=false + fi +} + +EvaluateVar () { + if [ -z ${!1+x} ]; then + if [ -z "${!2}" ]; then + echo "'' " + else + echo "${!2} " + fi + else + if [ -z "${!1}" ]; then + echo "''" + else + echo "${!1}" + fi + fi +} + +EndMenu () { + echo + echo "u to unset the value of key (revert to the default value)." + echo "q to return to the main menu" + read -p "Please input: # omit value to toggle a true/false : " key value +} + +LinkMenu () { + key='' + while [[ "$key" != q* ]]; do + clear + echo + echo " Link/D-Plus Menu" + echo + echo " 'CSV' means Comma Separated Values (of callsigns)" + echo " An empty CVS means everybody has permission" + echo -n "ad : CSV of calls that can execute scripts = "; EvaluateVar link_admin{,_d} + echo -n "li : CSV of calls that can link and unlink = "; EvaluateVar link_link_unlink{,_d} + echo -n "n : CSV of calls that cannot link&unlink = "; EvaluateVar link_no_link_unlink{,_d} + if [ -n "$em" ]; then + echo -n "i : Incoming IP address of QnetLink = "; EvaluateVar link_incoming_ip{,_d} + echo -n "r : UDP port for REF linking = "; EvaluateVar link_ref_port{,_d} + echo -n "x : UDP port for XRF linking = "; EvaluateVar link_xrf_port{,_d} + echo -n "d : DCS port for XRF linking = "; EvaluateVar link_dcs_port{,_d} + echo -n "an : Announce linking = "; EvaluateVar link_announce{,_d} + echo -n "ac : Acknowledge link on each keyup = "; EvaluateVar link_acknowledge{,_d} + echo -n "m : Maximum # of dongles allowed = "; EvaluateVar link_max_dongles{,_d} + fi + echo + echo " Legacy D-Plus Repeaters and Reflectors" + echo -n "au : Authorize Legacy D-Plus Linking = "; EvaluateVar dplus_authorize{,_d} + echo -n "ap : DPlus Priority = "; EvaluateVar dplus_priority{,_d} + if [ -n "$em" ]; then + echo -n "lo : Login call for authorization server = "; EvaluateVar dplus_ref_login{,_d} + echo -n "RF : Add legacy reflectors to gateway list = "; EvaluateVar dplus_use_reflectors{,_d} + echo -n "RP : add legacy repeaters to gateway list = "; EvaluateVar dplus_use_repeaters{,_d} + fi + EndMenu + + if [[ "$key" == ad* ]]; then link_admin="${value^^}" + elif [[ "$key" == li* ]]; then link_link_unlink="${value^^}" + elif [[ "$key" == n* ]]; then link_no_link_unlink="${value^^}" + elif [[ "$key" == i* ]]; then link_incoming_ip="$value" + elif [[ "$key" == r* ]]; then link_ref_port="$value" + elif [[ "$key" == x* ]]; then link_xrf_port="$value" + elif [[ "$key" == d* ]]; then link_dcs_port="$value" + elif [[ "$key" == an* ]]; then SetBooleanValue link_announce "$value" + elif [[ "$key" == ac* ]]; then SetBooleanValue link_acknowledge "$value" + elif [[ "$key" == m* ]]; then link_max_dongles="$value" + elif [[ "$key" == au* ]]; then SetBooleanValue dplus_authorize "$value" + elif [[ "$key" == ap* ]]; then SetBooleanValue dplus_priority "$value" + elif [[ "$key" == lo* ]]; then dplus_ref_login="${value^^}" + elif [[ "$key" == RF* ]]; then SetBooleanValue dplus_use_reflectors "$value" + elif [[ "$key" == RP* ]]; then SetBooleanValue dplus_use_repeaters "$value" + elif [[ "$key" == u* ]]; then + if [[ "$value" == ad* ]]; then unset link_admin + elif [[ "$value" == li* ]]; then unset link_link_unlink + elif [[ "$value" == n* ]]; then unset link_no_link_unlink + elif [[ "$value" == i* ]]; then unset link_incoming_ip + elif [[ "$value" == r* ]]; then unset link_ref_port + elif [[ "$value" == x* ]]; then unset link_xrf_port + elif [[ "$value" == d* ]]; then unset link_dcs_port + elif [[ "$value" == an* ]]; then unset link_announce + elif [[ "$value" == ac* ]]; then unset link_acknowledge + elif [[ "$value" == m* ]]; then unset link_max_dongles + elif [[ "$value" == au* ]]; then unset dplus_authorize + elif [[ "$value" == ap* ]]; then unset dplus_priority + elif [[ "$value" == lo* ]]; then unset dplus_ref_login + elif [[ "$value" == RF* ]]; then unset dplus_use_reflectors + elif [[ "$value" == RP* ]]; then unset dplus_use_repeaters + fi + fi + done +} + +FileMenu () { + key='' + while [[ "$key" != q* ]]; do + clear + echo + if [ -z "$em" ]; then + echo " Debugging Menu" + else + echo " Debugging/Files/Timings Menu" + fi + echo + echo " Additional entries in log files (in /usr/local/var)" + echo -n "cl : Call(QSO) logging = "; EvaluateVar log_qso{,_d} + echo -n "il : IRC logging = "; EvaluateVar log_irc{,_d} + echo -n "dl : DTMF logging = "; EvaluateVar log_dtmf{,_d} + echo -n "xl : Debug logging = "; EvaluateVar log_debug{,_d} + echo " WARNING debug logging can produce large log file!" + if [ -n "$em" ]; then + echo + echo " Files and directories" + echo -n "sf : Repeater status file = "; EvaluateVar file_status{,_d} + echo -n "ed : Echo/Voicemail directory = "; EvaluateVar file_echotest{,_d} + echo -n "dd : DTMF directory = "; EvaluateVar file_dtmf{,_d} + echo -n "vf : QnetVoice filename = "; EvaluateVar file_qnvoicefile{,_d} + echo -n "gf : Gateways filename = "; EvaluateVar file_gwys{,_d} + echo -n "ad : Announce directory = "; EvaluateVar file_announce_dir{,_d} + echo + echo " Timing controls" + echo -n "et : Echo timeout (sec) = "; EvaluateVar timing_timeout_echo{,_d} + echo -n "vt : Voicemail timeout (sec) = "; EvaluateVar timing_timeout_voicemail{,_d} + echo -n "gt : G2 timeout (sec) = "; EvaluateVar timing_timeout_remote_g2{,_d} + echo -n "rt : Repeater timeout (sec) = "; EvaluateVar timing_timeout_local_rptr{,_d} + echo -n "pw : Echo play wait (sec) = "; EvaluateVar timing_play_wait{,_d} + echo -n "pd : Echo play delay (msec) = "; EvaluateVar timing_play_delay{,_d} + fi + EndMenu + + if [[ "$key" == sf* ]]; then file_status="$value" + elif [[ "$key" == ed* ]]; then file_echotest="$value" + elif [[ "$key" == dd* ]]; then file_dtmf="$value" + elif [[ "$key" == vf* ]]; then file_qnvoicefile="$value" + elif [[ "$key" == gf* ]]; then file_gwys="$value" + elif [[ "$key" == ad* ]]; then file_announce_dir="$value" + elif [[ "$key" == cl* ]]; then SetBooleanValue log_qso "$value" + elif [[ "$key" == il* ]]; then SetBooleanValue log_irc "$value" + elif [[ "$key" == dl* ]]; then SetBooleanValue log_dtmf "$value" + elif [[ "$key" == xl* ]]; then SetBooleanValue log_debug "$value" + elif [[ "$key" == et* ]]; then timing_timeout_echo="$value" + elif [[ "$key" == vt* ]]; then timing_timeout_voicemail="$value" + elif [[ "$key" == gt* ]]; then timing_timeout_remote_g2="$value" + elif [[ "$key" == rt* ]]; then timing_timeout_local_rptr="$value" + elif [[ "$key" == pw* ]]; then timing_play_wait="$value" + elif [[ "$key" == pd* ]]; then timing_play_delay="$value" + elif [[ "$key" == u* ]]; then + if [[ "$value" == sf* ]]; then unset file_status + elif [[ "$value" == ed* ]]; then unset file_echotest + elif [[ "$value" == dd* ]]; then unset file_dtmf + elif [[ "$value" == vf* ]]; then unset file_qnvoicefile + elif [[ "$value" == gf* ]]; then unset file_gwys + elif [[ "$value" == ad* ]]; then unset file_announce_dir + elif [[ "$value" == cl* ]]; then unset log_qso + elif [[ "$value" == il* ]]; then unset log_irc + elif [[ "$value" == dl* ]]; then unset log_dtmf + elif [[ "$value" == xl* ]]; then unset log_debug + elif [[ "$value" == et* ]]; then unset timing_timeout_echo + elif [[ "$value" == vt* ]]; then unset timing_timeout_voicemail + elif [[ "$value" == gt* ]]; then unset timing_timeout_remote_g2 + elif [[ "$value" == rt* ]]; then unset timing_timeout_local_rptr + elif [[ "$value" == pw* ]]; then unset timing_play_wait + elif [[ "$value" == pd* ]]; then unset timing_play_delay + fi + fi + done +} + +IrcddbMenu () { + key='' + while [[ "$key" != q* ]]; do + clear + echo + echo " IRCDDB Menu" + echo + echo "Routes to other clients using IRC Server A will be searched first" + echo + echo "l : Login - must be a legal callsign = '${ircddb_login}'" + echo -n "ha : Hostname for IRC Server A = "; EvaluateVar ircddb0_host{,_d} + if [ -n "$em" ]; then + echo -n "oa : IRC TCP port A = "; EvaluateVar ircddb0_port{,_d} + echo -n "aa : IRC Password A = "; EvaluateVar ircddb0_password{,_d} + fi + echo -n "hb : Hostname for IRC Server B = "; EvaluateVar ircddb1_host{,_d} + if [ -n "$em" ]; then + echo -n "ob : IRC TCP port B = "; EvaluateVar ircddb1_port{,_d} + echo -n "ab : IRC Password B = "; EvaluateVar ircddb1_password{,_d} + fi + EndMenu + + if [[ "$key" == l* ]]; then ircddb_login="${value^^}" + elif [[ "$key" == ha* ]]; then ircddb0_host="$value" + elif [[ "$key" == oa* ]]; then ircddb0_port="$value" + elif [[ "$key" == aa* ]]; then ircddb0_password="$value" + elif [[ "$key" == hb* ]]; then ircddb1_host="$value" + elif [[ "$key" == ob* ]]; then ircddb1_port="$value" + elif [[ "$key" == ab* ]]; then ircddb1_password="$value" + elif [[ "$key" == u* ]]; then + if [[ "$value" == l* ]]; then unset ircddb_login + elif [[ "$value" == ha* ]]; then unset ircddb0_host + elif [[ "$value" == oa* ]]; then unset ircddb0_port + elif [[ "$value" == aa* ]]; then unset ircddb0_password + elif [[ "$value" == hb* ]]; then unset ircddb1_host + elif [[ "$value" == ob* ]]; then unset ircddb1_port + elif [[ "$value" == ab* ]]; then unset ircddb1_password + fi + fi + done +} + +GateMenu () { + key='' + while [[ "$key" != q* ]]; do + clear + echo + echo " Gateway/APRS Menu" + echo + echo " Gateway Option" + if [ -n "$em" ]; then + echo -n "r : Regenerate Headers = "; EvaluateVar gateway_header_regen{,_d} + echo -n "s : Send IRC network Module Info = "; EvaluateVar gateway_send_qrgs_maps{,_d} + echo -n "i : IRC TCP local network address = "; EvaluateVar gateway_local_irc_ip{,_d} + echo -n "a : G2 port address = "; EvaluateVar gateway_ip{,_d} + echo -n "p : G2 port number = "; EvaluateVar gateway_port{,_d} + echo -n "tl : UNIX socket to QnetLink = "; EvaluateVar gateway_gate2link{,_d} + echo -n "fl : UNIX socket from QnetLink = "; EvaluateVar gateway_link2gate{,_d} + echo -n "ta : UNIX socket to Modem A = "; EvaluateVar gateway_gate2modema{,_d} + echo -n "tb : UNIX socket to Modem B = "; EvaluateVar gateway_gate2modemb{,_d} + echo -n "tc : UNIX socket to Modem C = "; EvaluateVar gateway_gate2modemc{,_d} + echo -n "fm : UNIX socket from Modem(s) = "; EvaluateVar gateway_modem2gate{,_d} + fi + echo -n "la : Latitude (-90.0 to 90.0) = "; EvaluateVar gateway_latitude{,_d} + echo -n "lo : Longitude (-180.0 to 180.0) = "; EvaluateVar gateway_longitude{,_d} + echo -n "d1 : Description #1 (20 chars max) = "; EvaluateVar gateway_desc1{,_d} + echo -n "d2 : Description #1 (20 chars max) = "; EvaluateVar gateway_desc2{,_d} + echo -n "w : URL (80 char max) = "; EvaluateVar gateway_url{,_d} + echo -n "fr : Find Route(s) = "; EvaluateVar gateway_find_route{,_d} + echo " Find Route(s) is a comma-separated list of common routing callsigns." + echo " These will be added to your local cache on boot-up." + echo + echo " APRS - Repeater/User position tracking" + echo -n "e : Enable APRS Tracking = "; EvaluateVar aprs_enable{,_d} + if [ -n "$em" ]; then + echo -n "h : APRS hostname = "; EvaluateVar aprs_host{,_d} + echo -n "ap : APRS TCP port number = "; EvaluateVar aprs_port{,_d} + echo -n "k : APRS Keep-alive interval (min) = "; EvaluateVar aprs_interval{,_d} + echo -n "af : APRS Filter (experimental) = "; EvaluateVar aprs_filter{,_d} + fi + EndMenu + + if [[ "$key" == r* ]]; then SetBooleanValue gateway_header_regen "$value" + elif [[ "$key" == s* ]]; then SetBooleanValue gateway_send_qrgs_maps "$value" + elif [[ "$key" == i* ]]; then gateway_local_irc_ip="$value" + elif [[ "$key" == a* ]]; then gateway_ip="$value" + elif [[ "$key" == p* ]]; then gateway_port="$value" + elif [[ "$key" == tl* ]]; then gateway_gate2link="$value" + elif [[ "$key" == fl* ]]; then gateway_link2gate="$value" + elif [[ "$key" == ta* ]]; then gateway_gate2modema="$value" + elif [[ "$key" == tb* ]]; then gateway_gate2modemb="$value" + elif [[ "$key" == tc* ]]; then gateway_gate2modemc="$value" + elif [[ "$key" == fm* ]]; then gateway_modem2gate="$value" + elif [[ "$key" == la* ]]; then gateway_latitude="$value" + elif [[ "$key" == lo* ]]; then gateway_longitude="$value" + elif [[ "$key" == d1* ]]; then gateway_desc1="${value:0:20}" + elif [[ "$key" == d2* ]]; then gateway_desc2="${value:0:20}" + elif [[ "$key" == w* ]]; then gateway_url="${value:0:80}" + elif [[ "$key" == fr* ]]; then gateway_find_route="${value^^}" + elif [[ "$key" == e* ]]; then SetBooleanValue aprs_enable "$value" + elif [[ "$key" == h* ]]; then aprs_host="$value" + elif [[ "$key" == ap* ]]; then aprs_port="$value" + elif [[ "$key" == k* ]]; then aprs_interval="$value" + elif [[ "$key" == af* ]]; then aprs_filter="$value" + elif [[ "$key" == u* ]]; then + if [[ "$value" == r* ]]; then unset gateway_header_regen + elif [[ "$value" == s* ]]; then unset gateway_send_qrgs_maps + elif [[ "$value" == i* ]]; then unset gateway_local_irc_ip + elif [[ "$value" == a* ]]; then unset gateway_ip + elif [[ "$value" == p* ]]; then unset gateway_port + elif [[ "$value" == tl* ]]; then unset gateway_gate2link + elif [[ "$value" == ta* ]]; then unset gateway_gate2modema + elif [[ "$value" == tb* ]]; then unset gateway_gate2modemb + elif [[ "$value" == tc* ]]; then unset gateway_gate2modemc + elif [[ "$value" == fm* ]]; then unset gateway_modem2gate + elif [[ "$value" == la* ]]; then unset gateway_latitude + elif [[ "$value" == lo* ]]; then unset gateway_longitude + elif [[ "$value" == d1* ]]; then unset gateway_desc1 + elif [[ "$value" == d2* ]]; then unset gateway_desc2 + elif [[ "$value" == w* ]]; then unset gateway_url + elif [[ "$value" == fr* ]]; then unset gateway_find_route + elif [[ "$value" == e* ]]; then unset aprs_enable + elif [[ "$value" == h* ]]; then unset aprs_host + elif [[ "$value" == ap* ]]; then unset aprs_port + elif [[ "$value" == k* ]]; then unset aprs_interval + elif [[ "$value" == af* ]]; then unset aprs_filter + fi + fi + done +} + +ModuleMenu () { + mod=module_${1} + if [[ $1 == a ]]; then + nmod='a' + elif [[ $1 == b ]]; then + nmod='b' + else + nmod='c' + fi + clear + if [ -z ${!mod} ]; then + echo + echo " Select a Module type" + echo + echo "1 : DVAP Dongle" + echo "2 : DVRPTR V1" + echo "3 : ICOM Terminal and Access Point Mode" + echo "4 : MMDVM Modem (like DVMega or ZUMspot) D-Star ONLY!" + echo "5 : MMDVMHost-based Sytem - Requires MMDVMHost" + echo + echo " Anything else will return without selecting" + echo + echo -n "Select Module Type : " + read key unused + if [[ "$key" == 1 ]]; then eval ${mod}=dvap + elif [[ "$key" == 2 ]]; then eval ${mod}=dvrptr + elif [[ "$key" == 3 ]]; then eval ${mod}=itap + elif [[ "$key" == 4 ]]; then eval ${mod}=mmdvmmodem + elif [[ "$key" == 5 ]]; then eval ${mod}=mmdvmhost + else return + fi + fi + key='' + while [[ "$key" != q* ]]; do + clear + echo + echo " Module ${1^^} Menu ($mod=${!mod})" + echo + echo -n "ls : Link at startup (must be 8 chars) = "; EvaluateVar {${mod},module_x}_link_at_start + if [ -n "$em" ]; then + echo -n "ar : Auto relink in the case of a timeout = "; EvaluateVar {${mod},module_x}_auto_link + fi + echo -n "cs : Callsign (uses ircddb_login if empty) = "; EvaluateVar {${mod},module_x}_callsign + echo -n "ra : Range (in meters, 1 mile=1609.344 meters) = "; EvaluateVar {${mod},module_x}_range + echo -n "ag : Above ground level (in meters) = "; EvaluateVar {${mod},module_x}_agl + if [ -n "$em" ]; then + echo -n "in : Inactivity for this many minutes unlinks = "; EvaluateVar {${mod},module_x}_inactivity + echo -n "ac : Send acknowledgment on each transmission = "; EvaluateVar {${mod},module_x}_acknowledge + echo -n "ad : acknowledgment delay (in msec) = "; EvaluateVar {${mod},module_x}_ack_delay + echo -n "pw : in msec, packet wait time (test for timeout) = "; EvaluateVar {${mod},module_x}_packet_wait + fi + if [[ "${!mod}" == 'dvap' ]]; then + echo -n "fr : Frequency in MHz = "; EvaluateVar {${mod},dvap}_frequency + echo -n "of : Offset in Hz = "; EvaluateVar {${mod},dvap}_offset + echo -n "po : Power (in dBm from -12 to 10) = "; EvaluateVar {${mod},dvap}_power + echo -n "sq : Squelch (in dBm from -128 to -45) = "; EvaluateVar {${mod},dvap}_squelch + echo -n "sn : Serial # (visible through the case) = "; EvaluateVar {${mod},dvap}_serial_number + elif [[ "${!mod}" == 'dvrptr' ]]; then + echo -n "tx : Transmit frequency, in MHz = "; EvaluateVar {${mod},dvrptr}_tx_frequency + echo -n "rx : Receive frequency, in MHz = "; EvaluateVar {${mod},dvrtpr}_rx_frequency + echo -n "sn : Serial # (run once and look in log) = "; EvaluateVar {${mod},dvrptr}_serial_number + echo -n "rn : Callsign to turn RF on = "; EvaluateVar {${mod},dvrptr}_rf_on + echo -n "rf : Callsign to turn RF off = "; EvaluateVar {${mod},dvrptr}_rf_off + echo -n "rl : Receiver level = "; EvaluateVar {${mod},dvrptr}_rx_level + echo -n "du : Is duplex = "; EvaluateVar {${mod},dvrptr}_duplex + echo -n "td : Transmitter delay (in msec) for tx/rx switch = "; EvaluateVar {${mod},dvrptr}_tx_delay + echo -n "rq : # of 2 sec interval before system reset = "; EvaluateVar {${mod},dvrptr}_rqst_count + echo -n "ir : Inverse phase of receiver = "; EvaluateVar {${mod},dvrptr}_rx_invert + echo -n "it : Inverse phase of transmitter = "; EvaluateVar {${mod},dvrptr}_tx_invert + elif [[ "${!mod}" == 'itap' ]]; then + echo -n "fr : Frequency in MHz = "; EvaluateVar {${mod},itap}_frequency + echo -n "dv : USB device path = "; EvaluateVar {${mod},itap}_device + elif [[ "${!mod}" == 'mmdvmhost' ]]; then + echo -n "tx : Transmit frequency, in MHz = "; EvaluateVar {${mod},mmdvmhost}_tx_frequency + echo -n "rx : Receive frequency, in MHz = "; EvaluateVar {${mod},mmdvmhost}_rx_frequency + echo -n "ip : Internal IP address = "; EvaluateVar {${mod},mmdvmhost}_internal_ip + echo -n "gp : Gateway port number = "; EvaluateVar {${mod},mmdvmhost}_gateway_port + echo -n "lp : Local port number = "; EvaluateVar {${mod},mmdvmhost}_local_port + elif [[ "${!mod}" == 'mmdvmmodem' ]]; then + echo -n "dv : Device path = "; EvaluateVar {${mod},mmdvmmodem}_device + echo -n "tx : Transmit frequency, in MHz = "; EvaluateVar {${mod},mmdvmmodem}_tx_frequency + echo -n "rx : Receive frequency, in MHz = "; EvaluateVar {${mod},mmdvmmodem}_rx_frequency + echo -n "to : Transmit offset, in MHz = "; EvaluateVar {${mod},mmdvmmodem}_tx_offset + echo -n "r0 : Receive offset, in MHz = "; EvaluateVar {${mod},mmdvmmodem}_rx_offset + echo -n "du : Is duplex = "; EvaluateVar {${mod},mmdvmmodem}_duplex + if [ -n "$em" ]; then + echo -n "it : Transmit invert = "; EvaluateVar {${mod},mmdvmmodem}_tx_invert + echo -n "ir : Receive invert = "; EvaluateVar {${mod},mmdvmmodem}_rx_invert + echo -n "pi : PTT invert = "; EvaluateVar {${mod},mmdvmmodem}_ptt_invert + echo -n "td : Transmit delay (in msec) for tx/rx switch = "; EvaluateVar {${mod},mmdvmmodem}_tx_delay + echo -n "tl : Transmit level (0-255) = "; EvaluateVar {${mod},mmdvmmodem}_tx_level + echo -n "rl : Receive level (0-255) = "; EvaluateVar {${mod},mmdvmmodem}_rx_level + fi + fi + echo "xx : Delete this module" + EndMenu + + if [[ "$key" == ls* ]]; then + value="${value:0:8}" + eval ${mod}_link_at_start="'${value^^}'" + elif [[ "$key" == ac* ]]; then SetBooleanValue ${mod}_acknowledge "$value" + elif [[ "$key" == ad* ]]; then eval ${mod}_ack_delay="$value" + elif [[ "$key" == ag* ]]; then eval ${mod}_agl="$value" + elif [[ "$key" == ar* ]]; then SetBooleanValue ${mod}_auto_link "$value" + elif [[ "$key" == cs* ]]; then eval ${mod}_callsign="${value^^}" + elif [[ "$key" == du* ]]; then SetBooleanValue ${mod}_duplex "$value" + elif [[ "$key" == dv* ]]; then eval ${mod}_device="$value" + elif [[ "$key" == fr* ]]; then eval ${mod}_frequency="$value" + elif [[ "$key" == gp* ]]; then eval ${mod}_gateway_port="$value" + elif [[ "$key" == in* ]]; then eval ${mod}_inactivity="$value" + elif [[ "$key" == ir* ]]; then SetBooleanValue ${mod}_rx_invert "$value" + elif [[ "$key" == ip* ]]; then eval ${mod}_internal_ip="$value" + elif [[ "$key" == it* ]]; then SetBooleanValue ${mod}_tx_invert "$value" + elif [[ "$key" == lp* ]]; then eval ${mod}_local_port="$value" + elif [[ "$key" == of* ]]; then eval ${mod}_offset="$value" + elif [[ "$key" == pi* ]]; then SetBooleanValue ${mod}_ptt_invert="$value" + elif [[ "$key" == po* ]]; then eval ${mod}_power="$value" + elif [[ "$key" == pw* ]]; then eval ${mod}_packet_wait="$value" + elif [[ "$key" == ra* ]]; then eval ${mod}_range="$value" + elif [[ "$key" == rf* ]]; then eval ${mod}_rf_off="$value" + elif [[ "$key" == rl* ]]; then eval ${mod}_rx_level="$value" + elif [[ "$key" == rn* ]]; then eval ${mod}_rf_on="$value" + elif [[ "$key" == ro* ]]; then eval ${mod}_rx_offset="$value" + elif [[ "$key" == rq* ]]; then eval ${mod}_rqst_count="$value" + elif [[ "$key" == rx* ]]; then eval ${mod}_rx_frequency="$value" + elif [[ "$key" == sn* ]]; then eval ${mod}_serial_number="${value^^}" + elif [[ "$key" == sq* ]]; then eval ${mod}_squelch="$value" + elif [[ "$key" == td* ]]; then eval ${mod}_tx_delay="$value" + elif [[ "$key" == tl* ]]; then eval ${mod}_tx_level="$value" + elif [[ "$key" == to* ]]; then eval ${mod}_tx_offset="$value" + elif [[ "$key" == tx* ]]; then eval ${mod}_tx_frequency="$value" + elif [[ "$key" == xx* ]]; then + unset ${mod}_{link_at_start,callsign,{,rx_,tx_}frequency,offset,range,agl} + unset ${mod}_{inactivity,packet_wait,acknowledge,ack_delay,power,squelch,serial_number,rf_o{n,ff},{r,t}x_level} + unset ${mod}_{duplex,tx_delay,rqst_count,{tx,rx,ptt}_invert,device,internal_ip,{gateway,local}_port} + unset ${mod}_{tx_offset,rx_offset,auto_link} + unset ${mod} + return + elif [[ "$key" == u* ]]; then + if [[ "$value" == ac* ]]; then unset ${mod}_acknowledge + elif [[ "$value" == ad* ]]; then unset ${mod}_ack_delay + elif [[ "$value" == ar* ]]; then unset ${mod}_auto_link + elif [[ "$value" == ag* ]]; then unset ${mod}_agl + elif [[ "$value" == cs* ]]; then unset ${mod}_callsign + elif [[ "$value" == du* ]]; then unset ${mod}_duplex + elif [[ "$value" == dv* ]]; then unset ${mod}_device + elif [[ "$value" == fr* ]]; then unset ${mod}_frequency + elif [[ "$value" == gp* ]]; then unset ${mod}_gateway_port + elif [[ "$value" == in* ]]; then unset ${mod}_inactivity + elif [[ "$value" == ir* ]]; then unset ${mod}_rx_invert + elif [[ "$value" == ip* ]]; then unset ${mod}_internal_ip + elif [[ "$value" == it* ]]; then unset ${mod}_tx_invert + elif [[ "$value" == lp* ]]; then unset ${mod}_local_port + elif [[ "$value" == ls* ]]; then unset ${mod}_link_at_start + elif [[ "$value" == of* ]]; then unset ${mod}_offset + elif [[ "$value" == pi* ]]; then unset ${mod}_ppt_invert + elif [[ "$value" == po* ]]; then unset ${mod}_power + elif [[ "$value" == pw* ]]; then unset ${mod}_packet_wait + elif [[ "$value" == ra* ]]; then unset ${mod}_range + elif [[ "$value" == rf* ]]; then unset ${mod}_rf_off + elif [[ "$value" == rl* ]]; then unset ${mod}_rx_level + elif [[ "$value" == rn* ]]; then unset ${mod}_rf_on + elif [[ "$value" == ro* ]]; then unset ${mod}_rx_offset + elif [[ "$value" == rq* ]]; then unset ${mod}_rqst_count + elif [[ "$value" == rx* ]]; then unset ${mod}_rx_frequency + elif [[ "$value" == sn* ]]; then unset ${mod}_serial_number + elif [[ "$value" == sq* ]]; then unset ${mod}_squelch + elif [[ "$value" == td* ]]; then unset ${mod}_tx_delay + elif [[ "$value" == tl* ]]; then unset ${mod}_tx_level + elif [[ "$value" == to* ]]; then unset ${mod}_tx_offset + elif [[ "$value" == tx* ]]; then unset ${mod}_tx_frequency + fi + fi + done +} + +WriteCFGFile () { + local m p q outFile + if [ -z "$ircddb_login" ]; then + echo "You MUST set your ircddb login callsign (in the ircddb section)!" + read -p "Press to continue: " ans + return + fi + outFile='./qn.cfg' + echo "# Created on `date`" > $outFile + # ircddb_ section + echo "ircddb_login='$ircddb_login'" >> $outFile + [ -z "${ircddb0_host+x}" ] || echo "ircddb0_host='${ircddb0_host}'" >> $outFile + [ -z "${ircddb0_port+x}" ] || echo "ircddb0_host=${ircddb0_port}" >> $outFile + [ -z "${ircddb0_password+x}" ] || echo "ircddb0_password='${ircddb0_password}'" >> $outFile + [ -z "${ircddb1_host+x}" ] || echo "ircddb1_host='${ircddb1_host}'" >> $outFile + [ -z "${ircddb1_port+x}" ] || echo "ircddb1_host=${ircddb1_port}" >> $outFile + [ -z "${ircddb1_password+x}" ] || echo "ircddb1_password='${ircddb1_password}'" >> $outFile + + # module_?_ section + for m in a b c + do + p="module_$m" + if [ -n "${!p}" ]; then + echo "${p}=${!p}" >> $outFile + q=${p}_link_at_start; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile + q=${p}_auto_link; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_callsign; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile + q=${p}_offset; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_range; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_agl; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_inactivity; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_packet_wait; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_acknowledge; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_ack_delay; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + if [[ "${!p}" == "dvap" ]]; then + q=${p}_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_power; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_squelch; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_serial_number; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile + elif [[ "${!p}" == "dvrptr" ]]; then + q=${p}_tx_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_rx_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_serial_number; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile + q=${p}_rf_on; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile + q=${p}_rf_off; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile + q=${p}_rx_level; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_duplex; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_tx_delay; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_rqst_count; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_tx_invert; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_rx_invert; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + elif [[ "${!p}" == "itap" ]]; then + q=${p}_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_device; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + elif [[ "${!p}" == "mmdvmhost" ]]; then + q=${p}_tx_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_rx_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_internal_ip; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile + q=${p}_gateway_port; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_local_port; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + elif [[ "${!p}" == "mmdvmmodem" ]]; then + q=${p}_tx_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_rx_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_tx_offset; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_rx_offset; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_duplex; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_rx_level; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_tx_level; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_tx_invert; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_rx_invert; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_ptt_invert; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_tx_delay; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_device; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + fi + fi + done + # gateway_section + [ -z "${gateway_header_regen+x}" ] || echo "gateway_header_regen=${gateway_header_regen}" >> $outFile + [ -z "${gateway_send_qrgs_maps+x}" ] || echo "gateway_send_qrgs_maps=${gateway_send_qrgs_maps}" >> $outFile + [ -z "${gateway_local_irc_ip+x}" ] || echo "gateway_local_irc_ip='${gateway_local_irc_ip}'" >> $outFile + [ -z "${gateway_port+x}" ] || echo "gateway_port=${gateway_port}" >> $outFile + [ -z "${gateway_ip+x}" ] || echo "gateway_ip=${gateway_ip}" >> $outFile + [ -z "${gateway_tolink+x}" ] || echo "gateway_tolink=${gateway_tolink}" >> $outFile + [ -z "${gateway_fromlink+x}" ] || echo "gateway_fromlink=${gateway_fromlink}" >> $outFile + [ -z "${gateway_gate2modema+x}" ] || echo "gateway_gate2modema=${gateway_gate2modema}" >> $outFile + [ -z "${gateway_gate2modemb+x}" ] || echo "gateway_gate2modemb=${gateway_gate2modemb}" >> $outFile + [ -z "${gateway_gate2modemc+x}" ] || echo "gateway_gate2modemc=${gateway_gate2modemc}" >> $outFile + [ -z "${gateway_modem2gate+x}" ] || echo "gateway_modem2gate=${gateway_modem2gate}" >> $outFile + [ -z "${gateway_latitude+x}" ] || echo "gateway_latitude=${gateway_latitude}" >> $outFile + [ -z "${gateway_longitude+x}" ] || echo "gateway_longitude=${gateway_longitude}" >> $outFile + [ -z "${gateway_desc1+x}" ] || echo "gateway_desc1='${gateway_desc1}'" >> $outFile + [ -z "${gateway_desc2+x}" ] || echo "gateway_desc2='${gateway_desc2}'" >> $outFile + [ -z "${gateway_url+x}" ] || echo "gateway_url='${gateway_url}'" >> $outFile + [ -z "${gateway_find_route+x}" ] || echo "gateway_find_route='${gateway_find_route}'" >> $outFile + # arps_ section + [ -z "${aprs_enable+x}" ] || echo "aprs_enable=${aprs_enable}" >> $outFile + [ -z "${aprs_host+x}" ] || echo "aprs_host='${aprs_host}'" >> $outFile + [ -z "${aprs_port+x}" ] || echo "aprs_port=${aprs_port}" >> $outFile + [ -z "${aprs_interval+x}" ] || echo "aprs_interval=${aprs_interval}" >> $outFile + [ -z "${aprs_filter+x}" ] || echo "aprs_filter='${aprs_filter}'" >> $outFile + # link_ section + [ -z "${link_admin+x}" ] || echo "link_admin='${link_admin}'" >> $outFile + [ -z "${link_link_unlink+x}" ] || echo "link_link_unlink='${link_link_unlink}'" >> $outFile + [ -z "${link_no_link_unlink+x}" ] || echo "link_no_link_unlink='${link_no_link_unlink}'" >> $outFile + [ -z "${link_incoming_ip+x}" ] || echo "link_incoming_ip='${link_incoming_ip}'" >> $outFile + [ -z "${link_ref_port+x}" ] || echo "link_ref_port=${link_ref_port}" >> $outFile + [ -z "${link_xrf_port+x}" ] || echo "link_xrf_port=${link_xrf_port}" >> $outFile + [ -z "${link_dcs_port+x}" ] || echo "link_dcs_port=${link_dcs_port}" >> $outFile + [ -z "${link_announce+x}" ] || echo "link_announce=${link_announce}" >> $outFile + [ -z "${link_acknowledge+x}" ] || echo "link_acknowledge=${link_acknowledge}" >> $outFile + [ -z "${link_max_dongles+x}" ] || echo "link_max_dongles=${link_max_dongles}" >> $outFile + # log_ section + [ -z "${log_qso+x}" ] || echo "log_qso=${log_qso}" >> $outFile + [ -z "${log_irc+x}" ] || echo "log_irc=${log_irc}" >> $outFile + [ -z "${log_dtmf+x}" ] || echo "log_dtmf=${log_dtmf}" >> $outFile + [ -z "${log_debug+x}" ] || echo "log_debug=${log_debug}" >> $outFile + # dplus_ section + [ -z "${dplus_authorize+x}" ] || echo "dplus_authorize=${dplus_authorize}" >> $outFile + [ -z "${dplus_priority+x}" ] || echo "dplus_priority=${dplus_priority}" >> $outFile + [ -z "${dplus_ref_login+x}" ] || echo "dplus_ref_login='${dplus_ref_login}'" >> $outFile + [ -z "${dplus_use_repeaters+x}" ] || echo "dplus_use_repeaters=${dplus_use_repeaters}" >> $outFile + [ -z "${dplus_use_reflectors+x}" ] || echo "dplus_use_reflectors=${dplus_use_reflectors}" >> $outFile + # file_ section + [ -z "${file_gwys+x}" ] || echo "file_gwys='${file_gwys}'" >> $outFile + [ -z "${file_dtmf+x}" ] || echo "file_dtmf='${file_dtmf}'" >> $outFile + [ -z "${file_status+x}" ] || echo "file_status='${file_status}'" >> $outFile + [ -z "${file_echotest+x}" ] || echo "file_echotest='${file_echotest}'" >> $outFile + [ -z "${file_qnvoicefile+x}" ] || echo "file_qnvoicefile='${file_qnvoicefile}'" >> $outFile + [ -z "${file_announce_dir+x}" ] || echo "file_announce_dir='${file_announce_dir}'" >> $outFile + # timing_ section + [ -z "${timing_timeout_echo+x}" ] || echo "timing_timeout_echo=${timing_timeout_echo}" >> $outFile + [ -z "${timing_timeout_remote_g2+x}" ] || echo "timing_timeout_remote_g2=${timing_timeout_remote_g2}" >> $outFile + [ -z "${timing_timeout_voicemail+x}" ] || echo "timing_timeout_voicemail=${timing_timeout_voicemail}" >> $outFile + [ -z "${timing_timeout_local_rptr+x}" ] || echo "timing_timeout_local_rptr=${timing_timeout_local_rptr}" >> $outFile + [ -z "${timing_play_wait+x}" ] || echo "timing_play_wait=${timing_play_wait}" >> $outFile + [ -z "${timing_play_delay+x}" ] || echo "timing_play_delay=${timing_play_delay}" >> $outFile + clear + cat $outFile + echo + read -p "File written! Press to continue: " ans +} + +# Execution starts here! +# source files +if [ -e ./defaults ]; then + source ./defaults +else + echo 'Error: ./defaults not found!' + exit 1 +fi +if [ -e ./qn.cfg ]; then + source ./qn.cfg +else + echo 'No configuration file found...' + sleep 1 +fi + +# process arguments +if [[ "$1" == ex* ]]; then + em="expertMode" +fi + +# main loop +while [[ "$ans" != q* ]] +do + clear + echo + echo " QnConfig Main Menu V#190424" + echo + echo -n "a : Module A - "; if [ -z $module_a ]; then echo ""; else echo "${module_a^^}"; fi + echo -n "b : Module B - "; if [ -z $module_b ]; then echo ""; else echo "${module_b^^}"; fi + echo -n "c : Module C - "; if [ -z $module_c ]; then echo ""; else echo "${module_c^^}"; fi + echo "i : IRCDDB Menu - login = '${ircddb_login}'" + echo "g : Gateway/APRS Menu - location info and route cache" + echo "l : Link/D-Plus Menu - linking access" + if [ -n "$em" ]; then + echo "d : Debugging/Files/Timings - miscellaneous parameters" + else + echo "d : Debugging - additional log entries" + fi + echo + if [ -n $module_a ] || [ -n $module_b ] || [ -n $module_c ] && [ -n $ircddb_login ]; then + echo "w : Write qn.cfg configuration file (overwrites any existing file)" + echo + fi + read -p "q to quit: " ans + + if [[ "$ans" == a* ]]; then ModuleMenu a + elif [[ "$ans" == b* ]]; then ModuleMenu b + elif [[ "$ans" == c* ]]; then ModuleMenu c + elif [[ "$ans" == i* ]]; then IrcddbMenu + elif [[ "$ans" == g* ]]; then GateMenu + elif [[ "$ans" == l* ]]; then LinkMenu + elif [[ "$ans" == d* ]]; then FileMenu + elif [[ "$ans" == w* ]]; then WriteCFGFile + fi +done +[ -e qn.cfg ] && cat qn.cfg +exit 0 diff --git a/qndtmf.sh b/qndtmf.sh index 129ddfe..176501a 100755 --- a/qndtmf.sh +++ b/qndtmf.sh @@ -12,9 +12,26 @@ # Example: D00617 will link local module to DCS006 Q # Example: *00103 will link local module to REF001 C # Example: 0 or 00 will report status of the link, " I" +# Example: ##08 will execute the exec_H.sh script (shutdown the system) +# Please note that scripts exec_[0-9].sh are not accessible from DTMF. # We set this to spaces, it will be set later + +GetLetter () { + local i + if [[ $1 == +([0-9]) ]]; then + i=`expr $1 - 1` + if [ $i -ge 0 ] && [ $i -lt 26 ]; then + LETTER=${LETTERS[$i]} + return + fi + fi + LETTER=$BAD +} + LUSER=" " +LETTERS=( {A..Z} ) +BAD='bad' cd /tmp echo started at `date` @@ -25,101 +42,62 @@ do do echo found file $i at `date` LOCAL_BAND=${i:0:1} - if [[ "$LOCAL_BAND" = "A" ]] || [[ "$LOCAL_BAND" = "B" ]] || [[ "$LOCAL_BAND" = "C" ]] ; then + if [[ "$LOCAL_BAND" == 'A' ]] || [[ "$LOCAL_BAND" == 'B' ]] || [[ "$LOCAL_BAND" == 'C' ]]; then CMD=`head -n 1 $i 2>/dev/null` LUSER=`tail -n 1 $i 2>/dev/null` echo "... with these contents: " $CMD " " $LUSER - if [[ "$CMD" = "#" ]] ; then + if [[ "$CMD" == '#' ]]; then echo Unlinking local band $LOCAL_BAND requested by $LUSER qnremote ${LOCAL_BAND} "$LUSER" U >/dev/null 2>&1 echo - elif [[ "$CMD" = "0" ]] || [[ "$CMD" = "00" ]] ; then + elif [[ "$CMD" == '0' ]] || [[ "$CMD" == '00' ]]; then echo Link Status on local band $LOCAL_BAND requested by $LUSER qnremote ${LOCAL_BAND} "$LUSER" I >/dev/null 2>&1 echo - elif [[ "$CMD" = "**" ]] ; then + elif [[ "$CMD" == '**' ]]; then echo Load Hosts on local band $LOCAL_BAND requested by $LUSER - qnremote ${LOCAL_BAND} "$LUSER" L >/dev/null 2>&1 + qnremote ${LOCAL_BAND} "$LUSER" F >/dev/null 2>&1 else - LEN=${#CMD} - if [[ "$LEN" = "6" ]] ; then - PFX=${CMD:0:1} - REMOTE_NODE=${CMD:1:3} - REMOTE_BAND=${CMD:4:2} - - if [[ "$REMOTE_BAND" = "01" ]] ; then - REMOTE_BAND=A - elif [[ "$REMOTE_BAND" = "02" ]] ; then - REMOTE_BAND=B - elif [[ "$REMOTE_BAND" = "03" ]] ; then - REMOTE_BAND=C - elif [[ "$REMOTE_BAND" = "04" ]] ; then - REMOTE_BAND=D - elif [[ "$REMOTE_BAND" = "05" ]] ; then - REMOTE_BAND=E - elif [[ "$REMOTE_BAND" = "06" ]] ; then - REMOTE_BAND=F - elif [[ "$REMOTE_BAND" = "07" ]] ; then - REMOTE_BAND=G - elif [[ "$REMOTE_BAND" = "08" ]] ; then - REMOTE_BAND=H - elif [[ "$REMOTE_BAND" = "09" ]] ; then - REMOTE_BAND=I - elif [[ "$REMOTE_BAND" = "10" ]] ; then - REMOTE_BAND=J - elif [[ "$REMOTE_BAND" = "11" ]] ; then - REMOTE_BAND=K - elif [[ "$REMOTE_BAND" = "12" ]] ; then - REMOTE_BAND=L - elif [[ "$REMOTE_BAND" = "13" ]] ; then - REMOTE_BAND=M - elif [[ "$REMOTE_BAND" = "14" ]] ; then - REMOTE_BAND=N - elif [[ "$REMOTE_BAND" = "15" ]] ; then - REMOTE_BAND=O - elif [[ "$REMOTE_BAND" = "16" ]] ; then - REMOTE_BAND=P - elif [[ "$REMOTE_BAND" = "17" ]] ; then - REMOTE_BAND=Q - elif [[ "$REMOTE_BAND" = "18" ]] ; then - REMOTE_BAND=R - elif [[ "$REMOTE_BAND" = "19" ]] ; then - REMOTE_BAND=S - elif [[ "$REMOTE_BAND" = "20" ]] ; then - REMOTE_BAND=T - elif [[ "$REMOTE_BAND" = "21" ]] ; then - REMOTE_BAND=U - elif [[ "$REMOTE_BAND" = "22" ]] ; then - REMOTE_BAND=V - elif [[ "$REMOTE_BAND" = "23" ]] ; then - REMOTE_BAND=W - elif [[ "$REMOTE_BAND" = "24" ]] ; then - REMOTE_BAND=X - elif [[ "$REMOTE_BAND" = "25" ]] ; then - REMOTE_BAND=Y - elif [[ "$REMOTE_BAND" = "26" ]] ; then - REMOTE_BAND=Z + if [ ${#CMD} -eq 4 ] && [[ ${CMD:0:2} == '##' ]]; then + GetLetter ${CMD:2:2} + if [[ "$LETTER" == "$BAD" ]]; then + echo "bad script letter index: '${CMD:2:2}'" + qnvoice $LOCAL_BAND baddtmfcmd.dat "Bad DTMF CMD" else - REMOTE_BAND=Z + qnremote $LOCAL_BAND $LUSER ${LETTER}X >/dev/null 2>&1 fi + elif [ ${#CMD} -eq 6 ]; then - if [[ "$PFX" = "B" ]] ; then + PFX=${CMD:0:1} + if [[ "$PFX" = 'B' ]]; then RMT=XRF - elif [[ "$PFX" = "D" ]] ; then + elif [[ "$PFX" = 'D' ]]; then RMT=DCS - elif [[ "$PFX" = "*" ]] ; then + elif [[ "$PFX" = '*' ]]; then RMT=REF else - RMT=garbage + RMT=$BAD + fi + + REMOTE_NODE=${CMD:1:3} + if [[ $REMOTE_NODE != +([0-9]) ]]; then + REMOTE_NODE=$BAD fi - if [[ "$RMT" == "garbage" ]] ; then - echo bad value in prefix + GetLetter ${CMD:4:2} + REMOTE_BAND=$LETTER + + if [[ "$RMT" == "$BAD" ]] || [[ "$REMOTE_NODE" == "$BAD" ]] || [[ "$REMOTE_BAND" == "$BAD" ]]; then + echo "Bad link command: '$CMD'" + qnvoice $LOCAL_BAND baddtmfcmd.dat "Bad Link CMD" else echo linking local band $LOCAL_BAND to remote node ${RMT}${REMOTE_NODE} $REMOTE_BAND requested by $LUSER qnremote ${LOCAL_BAND} "$LUSER" ${RMT}${REMOTE_NODE}${REMOTE_BAND}L >/dev/null 2>&1 echo fi + else + echo "Bad command: '$CMD'" + qnvoice $LOCAL_BAND baddtmfcmd.dat "Bad CMD" fi fi else @@ -131,4 +109,3 @@ do done exit 0 - diff --git a/reflist.sh b/reflist.sh deleted file mode 100755 index aa50572..0000000 --- a/reflist.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -wget ftp://dschost1.w6kd.com/DExtra_Hosts.txt || wget ftp://dschost2.w6kd.com/DExtra_Hosts.txt -wget ftp://dschost1.w6kd.com/DPlus_Hosts.txt || wget ftp://dschost2.w6kd.com/DPlus_Hosts.txt -wget ftp://dschost1.w6kd.com/DCS_Hosts.txt || wget ftp://dschost2.w6kd.com/DCS_Hosts.txt - -/bin/rm -f gwys.txt - -echo "# Downloaded from dschost1.w6kd.com `date`" > gwys.txt -awk '$1 ~ /^REF/ { printf "%s %s 20001\n", $1, $2 }' DPlus_Hosts.txt >> gwys.txt -awk '$1 ~ /^XRF/ { printf "%s %s 30001\n", $1, $2 }' DExtra_Hosts.txt >> gwys.txt -awk '$1 ~ /^DCS/ { printf "%s %s 30051\n", $1, $2 }' DCS_Hosts.txt >> gwys.txt - -/bin/rm -f D{Extra,Plus,DSC}_Hosts.txt diff --git a/system/mmdvm.service b/system/mmdvm.service index 58f2760..75294d2 100644 --- a/system/mmdvm.service +++ b/system/mmdvm.service @@ -1,10 +1,10 @@ [Unit] Description=MMDVMHost -After=systemd-user-session.service +After=systemd-user-session.service qnrelay.service [Service] Type=simple -ExecStart=/usr/local/bin/MMDVMHost /usr/local/etc/MMDVM.qn +ExecStart=/usr/local/bin/XXX /usr/local/etc/YYY.qn Restart=always [Install] diff --git a/system/qndvap.service b/system/qndvap.service index 38031bb..1f425e0 100644 --- a/system/qndvap.service +++ b/system/qndvap.service @@ -1,10 +1,10 @@ [Unit] Description=QnetDVAP -After=systemd-user-session.service +After=systemd-user-session.service qngateway.service [Service] Type=simple -ExecStart=/usr/local/bin/qndvap /usr/local/etc/qn.cfg +ExecStart=/usr/local/bin/XXX /usr/local/etc/qn.cfg Restart=always [Install] diff --git a/system/qndvrptr.service b/system/qndvrptr.service index 18869cc..58cd008 100644 --- a/system/qndvrptr.service +++ b/system/qndvrptr.service @@ -1,10 +1,10 @@ [Unit] Description=QnetDVRPTR -After=systemd-user-session.service +After=systemd-user-session.service qngateway.service [Service] Type=simple -ExecStart=/usr/local/bin/qndvrptr /usr/local/etc/qn.cfg +ExecStart=/usr/local/bin/XXX /usr/local/etc/qn.cfg Restart=always [Install] diff --git a/system/qngateway.service b/system/qngateway.service index 1068d3c..911ccac 100644 --- a/system/qngateway.service +++ b/system/qngateway.service @@ -1,10 +1,12 @@ [Unit] Description=QnetGateway -After=systemd-user-session.service +Requires=network.target +After=systemd-user-session.service network.target [Service] Type=simple ExecStart=/usr/local/bin/qngateway /usr/local/etc/qn.cfg +Restart=always [Install] WantedBy=multi-user.target diff --git a/system/qnitap.service b/system/qnitap.service new file mode 100644 index 0000000..3930c0e --- /dev/null +++ b/system/qnitap.service @@ -0,0 +1,11 @@ +[Unit] +Description=QnetITAP +After=systemd-user-session.service qngateway.service + +[Service] +Type=simple +ExecStart=/usr/local/bin/XXX /usr/local/etc/qn.cfg +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/system/qnlink.service b/system/qnlink.service index 12ca713..8c68ea0 100644 --- a/system/qnlink.service +++ b/system/qnlink.service @@ -1,10 +1,12 @@ [Unit] Description=QnetLink -After=systemd-user-session.service +Requires=network.target +After=systemd-user-session.service network.target [Service] Type=simple ExecStart=/usr/local/bin/qnlink /usr/local/etc/qn.cfg +Restart=always [Install] WantedBy=multi-user.target diff --git a/system/qnmodem.service b/system/qnmodem.service new file mode 100644 index 0000000..2b86700 --- /dev/null +++ b/system/qnmodem.service @@ -0,0 +1,11 @@ +[Unit] +Description=QnetModem +After=systemd-user-session.service qngateway.service + +[Service] +Type=simple +ExecStart=/usr/local/bin/XXX /usr/local/etc/qn.cfg +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/system/qnrelay.service b/system/qnrelay.service index 823744d..96e011d 100644 --- a/system/qnrelay.service +++ b/system/qnrelay.service @@ -1,10 +1,11 @@ [Unit] Description=QnetRelay -After=systemd-user-session.service +After=systemd-user-session.service qngateway.service [Service] Type=simple -ExecStart=/usr/local/bin/qnrelay /usr/local/etc/qn.cfg +ExecStart=/usr/local/bin/XXX /usr/local/etc/qn.cfg +Restart=always [Install] WantedBy=multi-user.target diff --git a/versions.h b/versions.h deleted file mode 100644 index 932c849..0000000 --- a/versions.h +++ /dev/null @@ -1,6 +0,0 @@ -// version strings must be 55 characters or less! -#define IRCDDB_VERSION "linux-qngateway-6.1.0" -#define LINK_VERSION "5.2.0" -#define DVAP_VERSION "linux-qndvap-5.1.1" -#define DVRPTR_VERSION "linux-qndvrptr-5.1.0" -#define MMDVM_VERSION "mmdvm-qnmodem-0.1.0"