diff --git a/.gitignore b/.gitignore index 2971bab..949061f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,7 @@ *.o *.d .vscode -reflector/urfd.blacklist -reflector/urfd.whitelist -reflector/urfd.interlink -reflector/urfd.terminal -configure.mk -configure.h -configure.sql -reflector.cfg -wiresx/configure.php +reflector/urfd.* urfd +inicheck +dbutil diff --git a/config/urfd.ini b/config/urfd.ini new file mode 100644 index 0000000..e476ab6 --- /dev/null +++ b/config/urfd.ini @@ -0,0 +1,129 @@ +###### URFD CONFIGURATION Example ###### +# comments begin with '#' +# Do not use quotes, unless in a comment! + +[Names] +Callsign = URF??? # where ? is A-Z or 0-9 + +SysopEmail = me@somewhere.com + +# 2-letter country codes are listed at https://www.iban.com/country-codes +Country = GB + +Sponsor = My Home Club + +[IP Addresses] +# Binding addresses are usually the 'any' address +IPv4Binding = 0.0.0.0 + +# define IPv6 if you want a "dual-stack" reflector +# IPv6Binding = :: + +# define if you want to override what urfd finds using ipv4.icanhazip.com +# IPv4External = 4.3.2.1 + +# define if you want to override what urfd finds using ipv6.icanhazip.com +# IPv6External = f:e:d:c:b:a:9:0 + +Transcoder = local # SORRY, but only local TC's are supported right now! + +[Modules] +# Modules = ABCDEFGHIJKLMNOPQRSTUVWXYZ +Modules = ADMSZ +Transcoded = A # comment out if you don't have transcoding hardware +# Create Descriptions as needed... +DescriptionA = Transcoded +DescriptionD = DMR Chat +DescriptionM = M17 Chat +DescriptionS = DStar Chat +DescriptionZ = Temp Meeting + +# Protocols +[Brandmeister] +Enable = false # Set to true if you've configured BM connections in your urfd.interlink file. +Port = 10002 + +[DCS] +Port = 30051 + +[DExtra] +Port = 30001 + +[DPlus] +Port = 20001 + +[G3] +Enable = true + +[DMRPlus] +Port = 8880 + +[M17] +Port = 17000 + +[MMDVM] +Port = 62030 +DefaultId = 0 + +[NXDN] +Port = 41400 +AutoLinkModule = A # comment out if you want to disable AL +ReflectorID = 12345 + +[P25] +Port = 41000 +AutoLinkModule = A # comment out if you want to disable AL +ReflectorID = 12345 + +[URF] +Port = 10017 + +[USRP] +Enable = false +Callsign = ALLSTAR # set to NONE if you don't want to create this client +IPAddress = 1.2.3.4 # the IP address of the USRP client (the Allstar node) +RxPort = 34000 +TxPort = 32000 +Module = A # this has to be a transcoded module! +#FilePath = /home/usr/clients.txt # a list of listen-only clients + +[YSF] +Port = 42000 +AutoLinkModule = A # comment out if you want to disable AL +DefaultTxFreq = 446500000 +DefaultRxFreq = 446500000 +# if you've registered your reflector at register.ysfreflector.de: +RegistrationID = 12345 +RegistrationName = US URF??? +RegistrationDescription = URF Reflector + +######## Database files +[DMR ID DB] +Mode = http #### Mode is "http", "file", or "both" + #### if "both", the url will be read first +FilePath = /home/user/dmrid.dat # for you to add your own values + # will be reloaded within 10s +URL = http://xlxapi.rlx.lu/api/exportdmr.php # if Mode "http" or "both" +RefreshMin = 179 + +[NXDN ID DB] +Mode = http +FilePath = /home/user/nxdn.dat +URL = https://radioid.net/static/nxdn.csv +RefreshMin = 1440 # radioid.net says this file is updated once/day + +[YSF TX/RX DB] +Mode = http +FilePath = /home/user/ysfnode.dat +URL = http://xlxapi.rlx.lu/api/exportysfrepeaters.php +RefreshMin = 191 + +######### Other File locations +[Files] +PidPath = /var/run/xlxd.pid +XmlPath = /var/log/xlxd.xml +#JsonPath = /var/tmp/urfd.json # for future development +WhitelistPath = /home/user/urfd.whitelist +BlacklistPath = /home/user/urfd.blacklist +InterlinkPath = /home/user/urfd.interlink +G3TerminalPath = /home/user/urfd.terminal diff --git a/config/urfd.interlink b/config/urfd.interlink index b895a7d..3232cc2 100644 --- a/config/urfd.interlink +++ b/config/urfd.interlink @@ -5,12 +5,10 @@ # each entry specifies a remote XLX or XRF to peer with # format: # -# # example: # URF270 158.64.26.132 ACD -# XRF270 158.64.26.132 BB # -# note: the remote XLX must list this XLX in its interlink file +# note: the remote URFD must list this in its interlink file # for the link to be established # ############################################################################# diff --git a/config/urfd.mk b/config/urfd.mk new file mode 100644 index 0000000..641bc68 --- /dev/null +++ b/config/urfd.mk @@ -0,0 +1,6 @@ +# this is where the binary will be installed +BINDIR = /usr/local/bin + +# besides making an executable that gdb can use, +# this will also provide some additional log messsage +debug = false diff --git a/systemd/urfd.service b/config/urfd.service similarity index 78% rename from systemd/urfd.service rename to config/urfd.service index ada4503..0109457 100644 --- a/systemd/urfd.service +++ b/config/urfd.service @@ -5,7 +5,7 @@ After=systemd-user-session.service network.target [Service] Type=simple -ExecStart=/usr/local/bin/urfd +ExecStart=/usr/local/bin/urfd /PATH_TO_INI_FILE Restart=always [Install] diff --git a/radmin b/radmin index 1a136e3..ba8f9be 100755 --- a/radmin +++ b/radmin @@ -50,35 +50,13 @@ InstallReflector () { if [ ! -e urfd.interlink ]; then cp ../config/urfd.interlink . fi - if [[ "$g3support" == true ]] && [ ! -e urfd.terminal ]; then + if [ ! -e urfd.terminal ]; then cp ../config/urfd.terminal . fi sudo make install || read -p " to continue: " ans popd } -Clean () { - pushd reflector - make clean - popd - pushd ../tcd - make clean - popd -} - -Compile () { - local np - np=`getconf _NPROCESSORS_ONLN` - pushd reflector - make -j$np || read -p "" - echo "co : Compile the system executable(s)" if [ -e $urfserv ]; then if [ -e $urfserv ]; then echo "us : Uninstall the URF reflector" @@ -146,11 +131,7 @@ do echo read -p "Please input - omit value to toggle a true/false : " key value garbage - if [[ "$key" == ls* ]]; then - cat reflector.cfg; - echo - read -p " to return to main menu: " ans - elif [[ "$key" == us* ]]; then + if [[ "$key" == us* ]]; then if [ -e $urfserv ]; then UninstallReflector fi @@ -158,33 +139,8 @@ do if [ -e reflector/urfd ] && [ ! -e $urfserv ]; then InstallReflector fi - elif [[ "$key" == gp* ]]; then - echo - git pull - echo - read -p " to continue: " ans - pushd ../tcd - git pull - echo - read -p " to continue: " ans - popd - elif [[ "$key" == br* ]]; then - echo - git checkout "$value" - echo - read -p " to continue: " ans - pushd ../tcd - git checkout "$value" - echo - read -p " to continue: " ans - popd - elif [[ "$key" == rr* ]]; then - if [ -e $urfserv ]; then - sudo systemctl restart urfd - fi + elif [[ "$key" == rr* ]] && [ -e $urfserv ]; then sudo systemctl restart urfd elif [[ "$key" == tr* ]] && [ -e $tcdserv ]; then sudo systemctl restart tcd - elif [[ "$key" == cl* ]]; then Clean - elif [[ "$key" == co* ]]; then Compile elif [[ "$key" == tl* ]] && [ -e $tcdserv ]; then sudo journalctl -u tcd -f elif [[ "$key" == rl* ]] && [ -e $urfserv ]; then sudo journalctl -u urfd -f fi diff --git a/rconfig b/rconfig deleted file mode 100755 index 11a2216..0000000 --- a/rconfig +++ /dev/null @@ -1,653 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2021 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 3 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 . -#!/bin/bash - -CharCount () { - local haystack=$1 - local test="\${haystack//[^$2]}" - eval local result=$test - return ${#result} -} - -RemoveDupes () { - local -n s=$1 - local i j - for (( i=0; i<${#s}-1; i++)); do - for (( j=$i+1; j<${#s}; j++)); do - if [[ ${s:$i:1} != "." ]]; then - if [[ ${s:$i:1} == ${s:$j:1} ]]; then - local l=$(($j + 1)) - s="${s:0:$j}.${s:$l}" - fi - fi - done - done - s=${s//.} -} - -CheckModules () { - # only A through Z - modules=${1//[^A-Z]} - if (( ${#modules} < 1 )); then - unset modules - clear - echo "ERROR: You must specify at least one module, A to Z!" - echo - read -p " to continue: " ans - return - fi - - RemoveDupes modules - tcmodules=${modules:0:1} -} - -CheckTranscodedModules () { - local tc=${1//[^A-Z]} - local tcorig=$tc - if (( ${#tc} < 1 )); then - tcmodules=${modules:0:1} - clear - echo "ERROR: You must specify at least one module!" - echo "If you aren't using a transcoder, set the transcoder" - echo "address value to the default 'none'!" - echo - read -p " to continue: " ans - return - fi - - RemoveDupes tc - - local m=$modules_d - if [ ! -z ${modules+x} ]; then - m=$modules - fi - - local i - for ((i=0; i<${#tc}; i++)); do - CharCount $m ${tc:$i:1} - local count=$? - if (( $count < 1 )); then - local j=$((i+1)) - tc="${tc:0:$i}.${tc:$j}" - fi - done - tc=${tc//.} - - if (( ${#tc} < 1 )); then - echo "ERROR: Module(s), '$tcorig', are not in the configure modules, '$m'!" - echo - read -p " to continue: " ans - tcmodules=${modules:0:1} - return - fi - tcmodules=$tc -} - -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 -} - -TestGainValue () -{ - if [[ "$1" =~ ^[-]?[0-9]+$ ]]; then - if (( $1 >= -24 && $1 <= 24 )); then - return 0 - else - return 1 - fi - else - return 1 - fi -} - -AudioGainMenu () -{ -while [[ "$key" != q* ]] -do - clear - echo - echo " DVSI AMBE Chip Audio Gain Menu" - echo - echo " All values are in dB and can be from -24 to 24" - echo " 'input' means the audio amplitude is adjusted before encoding" - echo " 'output' means the audio amplitude is adjusted after decoding" - echo - echo " Non-zero values are generally not required!" - echo " USE AT YOUR OWN RISK!" - echo - echo -n "si : D-Star audio input = "; EvaluateVar dstar_in_gain{,_d} - echo -n "so : D-Star audio output = "; EvaluateVar dstar_out_gain{,_d} - echo -n "mi : DMR/YSF audio input = "; EvaluateVar dmr_in_gain{,_d} - echo -n "mo : DMR/YSF audio output = "; EvaluateVar dmr_out_gain{,_d} - echo - echo "q : Return to Main Menu" - echo "u : Unset the value of (revert to the default value)." - echo - read -p "Please input : " key value - if [[ "$key" == si* ]]; then - TestGainValue "$value" && dstar_in_gain="$value" - elif [[ "$key" == so* ]]; then - TestGainValue "$value" && dstar_out_gain="$value" - elif [[ "$key" == mi* ]]; then - TestGainValue "$value" && dmr_in_gain="$value" - elif [[ "$key" == mo* ]]; then - TestGainValue "$value" && dmr_out_gain="$value" - elif [[ "$key" == u* ]]; then - if [[ "$value" == si* ]]; then - unset dstar_in_gain - elif [[ "$value" == so* ]]; then - unset dstar_out_gain - elif [[ "$value" == mi* ]]; then - unset dmr_in_gain - elif [[ "$value" == mo* ]]; then - unset dmr_out_gain - fi - fi -done -} - -WriteMemFile () -{ - local file - file="$rcfg" - echo "# created on `date`" > $file - [ -z ${callsign+x} ] || echo "callsign='$callsign'" >> $file - [ -z ${modules+x} ] || echo "modules='$modules'" >> $file - [ -z ${ip4addr+x} ] || echo "ip4addr='$ip4addr'" >> $file - [ -z ${ip6addr+x} ] || echo "ip6addr='$ip6addr'" >> $file - [ -z ${tcaddress+x} ] || echo "tcaddress='$tcaddress'" >> $file - [ -z ${tcmodules+x} ] || echo "tcmodules='$tcmodules'" >> $file - [ -z ${dstar_in_gain+x} ] || echo "dstar_in_gain=$dstar_in_gain" >> $file - [ -z ${dstar_out_gain+x} ] || echo "dstar_out_gain=$dstar_out_gain" >> $file - [ -z ${dmr_in_gain+x} ] || echo "dmr_in_gain=$dmr_in_gain" >> $file - [ -z ${dmr_out_gain+x} ] || echo "dmr_out_gain=$dmr_out_gain" >> $file - [ -z ${dmrdbuseserver+x} ] || echo "dmrdbuseserver=$dmrdbuseserver" >> $file - [ -z ${dmrdbrefresh+x} ] || echo "dmrdbrefresh=$dmrdbrefresh" >> $file - [ -z ${dmrdbpath+x} ] || echo "dmrdbpath='$dmrdbpath'" >> $file - [ -z ${ysfautolink+x} ] || echo "ysfautolink=$ysfautolink" >> $file - [ -z ${ysfmodule+x} ] || echo "ysfmodule='$ysfmodule'" >> $file - [ -z ${ysflocaldb+x} ] || echo "ysflocaldb=$ysflocaldb" >> $file - [ -z ${ysfdbname+x} ] || echo "ysfdbname='$ysfdbname'" >> $file - [ -z ${ysfdbuser+x} ] || echo "ysfdbuser='$ysfdbuser'" >> $file - [ -z ${ysfrname+x} ] || echo "ysfrname='$ysfrname'" >> $file - [ -z ${ysfrdesc+x} ] || echo "ysfrdesc='$ysfrdesc'" >> $file - [ -z ${ysfdbpw+x} ] || echo "ysfdbpw='$ysfdbpw'" >> $file - [ -z ${g3support+x} ] || echo "g3support=$g3support" >> $file - [ -z ${dbsupport+x} ] || echo "dbsupport=$dbsupport" >> $file -} - -WriteSRCHFile () -{ - local file m - file="$srch" - echo "// Created on `date`" > $file - echo "#define CALLSIGN \"${callsign}\"" >> $file - if [[ "$callsign" == XRF* ]]; then - echo "#define NO_XLX" >> $file - fi - if [ -z ${modules+x} ]; then - echo "#define ACTIVE_MODULES \"${modules_d}\"" >> $file - else - echo "#define ACTIVE_MODULES \"${modules}\"" >> $file - fi - if [ ! -z ${ip4addr+x} ]; then - echo "#define LISTEN_IPV4 \"${ip4addr}\"" >> $file - fi - if [ ! -z ${ip6addr+x} ]; then - echo "#define LISTEN_IPV6 \"${ip6addr}\"" >> $file - fi - if [ -z ${ysfautolink+x} ]; then - echo "#define YSF_AUTOLINK_ENABLE ${ysfautolink_d}" >> $file - else - echo "#define YSF_AUTOLINK_ENABLE ${ysfautolink}" >> $file - fi - if [ -z ${ysfmodule+x} ]; then - echo "#define YSF_AUTOLINK_MODULE '${ysfmodule_d}'" >> $file - else - echo "#define YSF_AUTOLINK_MODULE '${ysfmodule}'" >> $file - fi - if [ ! -z ${dmrdbuseserver+x} ]; then - if [[ "$dmrdbuseserver" == true ]]; then - m=1 - else - m=0 - fi - else - m=1 - fi - echo "#define DMRIDDB_USE_RLX_SERVER $m" >> $file - if [ ! -z ${dmrdbrefresh+x} ]; then - echo "#define DMRIDDB_REFRESH_RATE $dmrdbrefresh" >> $file - else - echo "#define DMRIDDB_REFRESH_RATE $dmrdbrefresh_d" >> $file - fi - if [ ! -z ${dmrdbpath+x} ]; then - echo "#define DMRIDDB_PATH \"$dmrdbpath\"" >> $file - else - echo "#define DMRIDDB_PATH \"$dmrdbpath_d\"" >> $file - fi - if [[ "$ysflocaldb" == true ]]; then - echo '#define YSF_DB_SUPPORT true' >> $file - echo "#define YSF_DB_NAME \"$ysfdbname\"" >> $file - echo "#define YSF_DB_USER \"$ysfdbuser\"" >> $file - echo "#define YSF_DB_PASSWORD \"$ysfdbpw\"" >> $file - else - echo '#define YSF_DB_SUPPORT false' >> $file - fi - if [ ! -z ${ysfrname+x} ]; then - echo "#define YSF_REFLECTOR_NAME \"$ysfrname\"" >> $file - fi - if [ ! -z ${ysfrdesc+x} ]; then - echo "#define YSF_REFLECTOR_DESCRIPTION \"$ysfrdesc\"" >> $file - fi - if [ ! -z ${tcaddress+x} ]; then - echo "#define TRANSCODER_IP \"${tcaddress}\"" >> $file - echo "#define TRANSCODED_MODULES \"${tcmodules}\"" >> $file - fi - if [ -z ${g3support+x} ]; then - m=${g3support_d} - else - m=${g3support} - fi - if [[ "$m" != true ]]; then - echo "#define NO_G3" >> $file - fi - if [ -n ${dbsupport+x} ]; then - if [ "$dbsupport" == true ]; then - echo "#define DEBUG" >> $file - fi - fi -} - -WriteSRCMKFile () -{ - local file - file="$srcm" - echo "# Created on `date`" > $file - [ -z ${ip4addr+x} ] || echo "ipv4 = $ip4addr" >> $file - [ -z ${ip6addr+x} ] || echo "ipv6 = $ip6addr" >> $file - [ -z ${tcaddress+x} ] || echo "tc_ip = $tcaddress" >> $file - if [ -z ${g3support+x} ]; then - echo "use_g3 = $g3support_d" >> $file - else - echo "use_g3 = $g3support" >> $file - fi - if [ -z ${dbsupport+x} ]; then - echo "debug = $dbsupport_d" >> $file - else - echo "debug = $dbsupport" >> $file - fi - if [ -z ${ysflocaldb+x} ]; then - echo "ysf_db = $ysflocaldb_d" >> $file - else - echo "ysf_db = $ysflocaldb" >> $file - fi -} - -WriteTranscoderHFile () -{ - local file - file="$tcdh" - echo "// Created on `date`" > $file - if [ -n ${tcaddress+x} ] && [[ "$tcaddress" != "local" ]]; then - echo "#define TRANSCODER_IP \"${tcaddress}\"" >> $file - fi - if [ -n ${tcmodules+x} ]; then - echo "#define TRANSCODED_MODULES \"${tcmodules}\"" >> $file - fi - if [ -z ${dstar_in_gain+x} ]; then - echo "#define DSTAR_IN_GAIN $dstar_in_gain_d" >> $file - else - echo "#define DSTAR_IN_GAIN $dstar_in_gain" >> $file - fi - if [ -z ${dstar_out_gain+x} ]; then - echo "#define DSTAR_OUT_GAIN $dstar_out_gain_d" >> $file - else - echo "#define DSTAR_OUT_GAIN $dstar_out_gain" >> $file - fi - if [ -z ${dmr_in_gain+x} ]; then - echo "#define DMR_IN_GAIN $dmr_in_gain_d" >> $file - else - echo "#define DMR_IN_GAIN $dmr_in_gain" >> $file - fi - if [ -z ${dmr_out_gain+x} ]; then - echo "#define DMR_OUT_GAIN $dmr_out_gain_d" >> $file - else - echo "#define DMR_OUT_GAIN $dmr_out_gain" >> $file - fi - if [ -n ${dbsupport+x} ]; then - if [ "$dbsupport" == true ]; then - echo "#define DEBUG" >> $file - fi - fi -} - -WriteTranscoderMKFile () -{ - local file - file="$tcdm" - echo "# created on `date`" > $file - if [ -z ${dbsupport+x} ]; then - echo "debug = $dbsupport_d" >> $file - else - echo "debug = $dbsupport" >> $file - fi -} - -WriteCfgPhpFile () -{ - cat << EOF > $ysfs - -EOF -} - -WriteDBCreateFile () -{ - cat << EOF > $dbcr -CREATE DATABASE IF NOT EXISTS ${ysfdbname}; -USE ${ysfdbname}; -CREATE TABLE IF NOT EXISTS ysfnodes ( - callsign VARCHAR(7) PRIMARY KEY, - password VARCHAR(255) NOT NULL, - txfreq INT NOT NULL DEFAULT 446500000, - rxfreq INT NOT NULL DEFAULT 446500000, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP -); -CREATE USER IF NOT EXISTS '${ysfdbuser}'@'localhost' IDENTIFIED BY '${ysfdbpw}'; -GRANT ALL PRIVILEGES ON $ysfdbname . ysfnodes TO '${ysfdbuser}'@'localhost'; -FLUSH PRIVILEGES; -EOF -} - -WriteCFGFiles () -{ - WriteMemFile - WriteSRCHFile - WriteSRCMKFile - if [ -z ${tcaddress+x} ]; then - rm -f $tcdh $tcdm - else - WriteTranscoderHFile - WriteTranscoderMKFile - fi - if [[ "$ysflocaldb" == true ]]; then - WriteCfgPhpFile - WriteDBCreateFile - else - rm -f $ysfs $dbcr - fi -} - -ListCFGFiles () -{ - echo "===========${rcfg}=============" - cat $rcfg - echo "===========${srch}=============" - cat $srch - echo "===========${srcm}=============" - cat $srcm - if [ ! -z ${tcaddress+x} ]; then - echo "===========${tcdh}=============" - cat $tcdh - echo "===========${tcdm}=============" - cat $tcdm - fi - if [[ "$ysflocaldb" == true ]]; then - echo "===========${ysfs}=============" - cat $ysfs - echo "===========${dbcr}=============" - cat $dbcr - fi -} - -# Execution starts here! -# file locations -rcfg='reflector.cfg' -srch='reflector/configure.h' -srcm='reflector/configure.mk' -tcdh='../tcd/configure.h' -tcdm='../tcd/configure.mk' -ysfs='wiresx/configure.php' -dbcr='configure.sql' -urfserv='/etc/systemd/system/urfd.service' - -# expert mode -if [[ "$1" == ex* ]]; then - expertmode="expertMode" -fi - -if [ -e reflector.cfg ]; then - source reflector.cfg -else - echo 'No configuration file found...' - sleep 1 -fi - -# default values -callsign_d='CHNGME' -modules_d='ABCF' -ip4addr_d='none' -ip6addr_d='none' -tcaddress_d='none' -ysfautolink_d=false -ysfmodule_d='D' -g3support_d=false -dbsupport_d=false -dmrdbuseserver_d=true -dmrdbrefresh_d=180 -dmrdbpath_d='/usr/local/etc/dmrid.dat' -ysflocaldb_d=false -ysfdbname_d='' -ysfdbuser_d='' -ysfdbpw_d='' -dstar_in_gain_d=0 -dstar_out_gain_d=0 -dmr_in_gain_d=0 -dmr_out_gain_d=0 -if [ -z ${callsign+x} ];then - ysfrname_d="$callsign_d" -else - ysfrname_d="$callsign" -fi -ysfrdesc_d='URF Reflector' - -if [ -z ${expertmode+x} ]; then - if [ -e $urfserv ]; then - echo -n "You cannot change the configuration right now beacuse there is an " - if [ -e $urfserv ]; then - echo "URFD server running." - fi; - echo "===========${rcfg}=============" - cat $rcfg - echo - echo "Please use radmin to uninstall the running server before attempting to modify the configuration." - exit 1 - fi -fi - -key='x' -# main loop -while [[ "$key" != q* ]] -do - clear - echo - echo " Reflector Configuration, Version #220326" - echo - echo " ******* REFLECTOR ********" - echo -n "cs : Reflector Callsign = "; EvaluateVar callsign{,_d} - echo -n "am : Active Modules = "; EvaluateVar modules{,_d} - echo -n "g3 : Icom G3 Support = "; EvaluateVar g3support{,_d} - echo " ******* ADDRESSES ********" - echo -n "i4 : IPv4 Listen Address = "; EvaluateVar ip4addr{,_d} - echo -n "i6 : IPv6 Listen Address = "; EvaluateVar ip6addr{,_d} - echo " ******* TRANSCODER ********" - echo " The only TC address supported is 'local' or the default 'none'" - echo -n "tc : Transcoder Address = "; EvaluateVar tcaddress{,_d} - if [ ! -z ${tcaddress+x} ]; then - echo -n "tm : Transcoder Modules = "; EvaluateVar tcmodules{,_d} - echo "ag : AMBE Audio Gain Sub-Menu" - if [ ! -z ${dstar_in_gain+x} ]; then - echo -n " D-Star Input gain = "; EvaluateVar dstar_in_gain{,_d} - fi - if [ ! -z ${dstar_out_gain+x} ]; then - echo -n " D-Star Output gain = "; EvaluateVar dstar_out_gain{,_d} - fi - if [ ! -z ${dmr_in_gain+x} ]; then - echo -n " DMR Input gain = "; EvaluateVar dmr_in_gain{,_d} - fi - if [ ! -z ${dmr_out_gain+x} ]; then - echo -n " DMR Output gain = "; EvaluateVar dmr_out_gain{,_d} - fi - fi - echo " ******* DMR Database ********" - echo -n "ds : Use RLX Server = "; EvaluateVar dmrdbuseserver{,_d} - echo -n "dt : Refresh time (in min) = "; EvaluateVar dmrdbrefresh{,_d} - echo -n "dp : Database path = "; EvaluateVar dmrdbpath{,_d} - echo " ******* SYSTEM FUSION ********" - echo -n "ye : YSF Autolink Enable = "; EvaluateVar ysfautolink{,_d} - if [ ! -z ${ysfautolink+x} ]; then - if [[ "$ysfautolink" == true ]]; then - echo -n "ym : YSF Autolink Module = "; EvaluateVar ysfmodule{,_d} - fi - fi - echo -n "yl : YSF Local Database = "; EvaluateVar ysflocaldb{,_d} - if [[ "$ysflocaldb" == true ]]; then - echo -n "yd : YSF Database Name = "; EvaluateVar ysfdbname{,_d} - echo -n "yu : YSF Database User = "; EvaluateVar ysfdbuser{,_d} - echo -n "yp : YSF Database Password = "; EvaluateVar ysfdbpw{,_d} - fi - echo " ******* YSFReflector Registry *******" - echo -n "rn : Registry Name = "; EvaluateVar ysfrname{,_d} - echo -n "rd : Registry Description = "; EvaluateVar ysfrdesc{,_d} - echo " ******* DEBUGGING ********" - echo -n "db : Debugging Support = "; EvaluateVar dbsupport{,_d} - echo - if [[ "$callsign" == URF* ]]; then - echo "w : Write configuration files (overwrites any existing files) and quit" - fi - echo "q : Quit without saving" - echo "u : Unset the value of (revert to the default value)." - echo - read -p "Please input - omit value to toggle a true/false : " key value - - if [[ "$key" == cs* && ${value^^} == URF* ]]; then - callsign="${value^^}" - callsign="${callsign:0:6}" - ysfrname_d="${callsign}" - unset tcaddress tcmodules ysf{autolink,module,localdb,dbname,dbuser,dbpw} - elif [[ "$key" == am* ]]; then CheckModules "${value^^}" - elif [[ "$key" == i4* ]]; then ip4addr="$value" - elif [[ "$key" == i6* ]]; then ip6addr="$value" - elif [[ "$key" == tc* ]]; then tcaddress="local" - elif [[ "$key" == tm* ]]; then CheckTranscodedModules "${value^^}" - elif [[ "$key" == ag* ]]; then - AudioGainMenu - key=x - elif [[ "$key" == ds* ]]; then SetBooleanValue dmrdbuseserver "$value" - elif [[ "$key" == dt* ]]; then dmrdbrefresh="$value" - elif [[ "$key" == dp* ]]; then dmrdbpath="$value" - elif [[ "$key" == ye* ]]; then SetBooleanValue ysfautolink "$value" - elif [[ "$key" == ym* ]]; then - ysfmodule="${value^^}" - ysfmodule="${ysfmodule:0:1}" - elif [[ "$key" == g3* ]]; then SetBooleanValue g3support "$value" - elif [[ "$key" == db* ]]; then SetBooleanValue dbsupport "$value" - elif [[ "$key" == yl* ]]; then SetBooleanValue ysflocaldb "$value" - elif [[ "$key" == yd* ]]; then ysfdbname="$value" - elif [[ "$key" == yu* ]]; then ysfdbuser="$value" - elif [[ "$key" == yp* ]]; then ysfdbpw="$value" - elif [[ "$key" == rn* ]]; then ysfrname="${value:0:16}" - elif [[ "$key" == rd* ]]; then ysfrdesc="${value:0:14}" - elif [[ "$key" == w* ]]; then - WriteCFGFiles - ListCFGFiles - exit 0 - elif [[ "$key" == u* ]]; then - if [[ "$value" == cs* ]]; then unset callsign - elif [[ "$value" == am* ]]; then unset modules - elif [[ "$value" == i4* ]]; then unset ip4addr - elif [[ "$value" == i6* ]]; then unset ip6addr - elif [[ "$value" == tc* ]]; then unset tcaddress - elif [[ "$value" == tm* ]]; then tcmodules=${modules:0:1} - elif [[ "$value" == ds* ]]; then unset dmrdbuseserver - elif [[ "$value" == dt* ]]; then unset dmrdbrefresh - elif [[ "$value" == dp* ]]; then unset dmrdbpath - elif [[ "$value" == ye* ]]; then unset ysfautolink ysfmodule - elif [[ "$value" == ym* ]]; then unset ysfmodule - elif [[ "$value" == g3* ]]; then unset g3support - elif [[ "$value" == db* ]]; then unset dbsupport - elif [[ "$value" == yl* ]]; then unset ysflocaldb ysfdbname ysfdbuser ysfdbpw - elif [[ "$value" == yd* ]]; then unset ysfdbname - elif [[ "$value" == yu* ]]; then unset ysfdbuser - elif [[ "$value" == yp* ]]; then unset ysfdbpw - elif [[ "$value" == rn* ]]; then unset ysfrname - elif [[ "$value" == rd* ]]; then unset ysfrdesc - fi - fi -done -exit 0 diff --git a/reflector/BMClient.cpp b/reflector/BMClient.cpp index ed5caaa..9e621da 100644 --- a/reflector/BMClient.cpp +++ b/reflector/BMClient.cpp @@ -17,7 +17,7 @@ // along with this program. If not, see . #include -#include "Main.h" + #include "BMClient.h" @@ -43,5 +43,5 @@ CBmClient::CBmClient(const CBmClient &client) bool CBmClient::IsAlive(void) const { - return (m_LastKeepaliveTime.time() < XLX_KEEPALIVE_TIMEOUT); + return (m_LastKeepaliveTime.time() < BM_KEEPALIVE_TIMEOUT); } diff --git a/reflector/BMClient.h b/reflector/BMClient.h index 1708bf6..4328afc 100644 --- a/reflector/BMClient.h +++ b/reflector/BMClient.h @@ -18,6 +18,7 @@ #pragma once +#include "Defines.h" #include "Client.h" class CBmClient : public CClient diff --git a/reflector/BMPeer.cpp b/reflector/BMPeer.cpp index e688697..fa03e3c 100644 --- a/reflector/BMPeer.cpp +++ b/reflector/BMPeer.cpp @@ -17,7 +17,7 @@ // along with this program. If not, see . -#include "Main.h" + #include #include "Reflector.h" #include "BMPeer.h" @@ -50,7 +50,7 @@ CBmPeer::CBmPeer(const CCallsign &callsign, const CIp &ip, const char *modules, bool CBmPeer::IsAlive(void) const { - return (m_LastKeepaliveTime.time() < XLX_KEEPALIVE_TIMEOUT); + return (m_LastKeepaliveTime.time() < BM_KEEPALIVE_TIMEOUT); } //////////////////////////////////////////////////////////////////////////////////////// diff --git a/reflector/BMProtocol.cpp b/reflector/BMProtocol.cpp index 13a2c09..f16752d 100644 --- a/reflector/BMProtocol.cpp +++ b/reflector/BMProtocol.cpp @@ -18,18 +18,16 @@ #include -#include "Main.h" #include "BMPeer.h" #include "BMProtocol.h" -#include "Reflector.h" -#include "GateKeeper.h" - +#include "Global.h" //////////////////////////////////////////////////////////////////////////////////////// // operation bool CBMProtocol::Initialize(const char *type, const EProtocol ptype, const uint16_t port, const bool has_ipv4, const bool has_ipv6) { + m_HasTranscoder = g_Configure.IsString(g_Keys.modules.tcmodules); if (! CProtocol::Initialize(type, ptype, port, has_ipv4, has_ipv6)) return false; @@ -167,7 +165,7 @@ void CBMProtocol::Task(void) HandleQueue(); // keep alive - if ( m_LastKeepaliveTime.time() > XLX_KEEPALIVE_PERIOD ) + if ( m_LastKeepaliveTime.time() > BM_KEEPALIVE_PERIOD ) { // handle keep alives HandleKeepalives(); @@ -177,7 +175,7 @@ void CBMProtocol::Task(void) } // peer connections - if ( m_LastPeersLinkTime.time() > XLX_RECONNECT_PERIOD ) + if ( m_LastPeersLinkTime.time() > BM_RECONNECT_PERIOD ) { // handle remote peers connections HandlePeerLinks(); @@ -192,11 +190,10 @@ void CBMProtocol::Task(void) void CBMProtocol::HandleQueue(void) { - m_Queue.Lock(); - while ( !m_Queue.empty() ) + while (! m_Queue.IsEmpty()) { // get the packet - auto packet = m_Queue.pop(); + auto packet = m_Queue.Pop(); // encode it CBuffer buffer; @@ -228,11 +225,10 @@ void CBMProtocol::HandleQueue(void) break; case EProtoRev::ambe: default: -#ifdef TRANSCODED_MODULES - Send(buffer, client->GetIp()); -#else - Send(bufferLegacy, client->GetIp()); -#endif + if (m_HasTranscoder) + Send(buffer, client->GetIp()); + else + Send(bufferLegacy, client->GetIp()); break; } } @@ -240,7 +236,6 @@ void CBMProtocol::HandleQueue(void) g_Reflector.ReleaseClients(); } } - m_Queue.Unlock(); } //////////////////////////////////////////////////////////////////////////////////////// @@ -324,7 +319,7 @@ void CBMProtocol::HandlePeerLinks(void) it->ResolveIp(); // send connect packet to re-initiate peer link EncodeConnectPacket(&buffer, it->GetModules()); - Send(buffer, it->GetIp(), XLX_PORT); + Send(buffer, it->GetIp(), m_Port); std::cout << "Sending connect packet to BM peer " << cs << " @ " << it->GetIp() << " for modules " << it->GetModules() << std::endl; } } diff --git a/reflector/BMProtocol.h b/reflector/BMProtocol.h index fa36b2d..08c0593 100644 --- a/reflector/BMProtocol.h +++ b/reflector/BMProtocol.h @@ -18,6 +18,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#include "Defines.h" #include "Version.h" #include "Timer.h" #include "SEProtocol.h" @@ -72,4 +73,6 @@ protected: // time CTimer m_LastKeepaliveTime; CTimer m_LastPeersLinkTime; + // config data; + bool m_HasTranscoder; }; diff --git a/reflector/Buffer.cpp b/reflector/Buffer.cpp index 544ba84..c99d290 100644 --- a/reflector/Buffer.cpp +++ b/reflector/Buffer.cpp @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#include #include #include "Buffer.h" @@ -175,7 +176,7 @@ CBuffer::operator const char *() const void CBuffer::DebugDump(std::ofstream &debugout) const { - // dump an hex line + // dump a hex line for ( unsigned int i = 0; i < m_data.size(); i++ ) { char sz[16]; diff --git a/reflector/Buffer.h b/reflector/Buffer.h index deec049..12eb495 100644 --- a/reflector/Buffer.h +++ b/reflector/Buffer.h @@ -20,7 +20,6 @@ #include #include -#include "Main.h" //////////////////////////////////////////////////////////////////////////////////////// diff --git a/reflector/CNotificationQueue.h b/reflector/CNotificationQueue.h index 57839ad..f0bc210 100644 --- a/reflector/CNotificationQueue.h +++ b/reflector/CNotificationQueue.h @@ -23,12 +23,6 @@ #include "Notification.h" - -//////////////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////////////// -// class - class CNotificationQueue { public: diff --git a/reflector/Callsign.h b/reflector/Callsign.h index e436891..49bb6d9 100644 --- a/reflector/Callsign.h +++ b/reflector/Callsign.h @@ -27,6 +27,18 @@ #define CALLSIGN_LEN 8 #define CALLSUFFIX_LEN 4 +union UCallsign +{ + char c[CALLSIGN_LEN]; + uint64_t l; +}; + +union USuffix +{ + char c[CALLSUFFIX_LEN]; + uint32_t u; +}; + //////////////////////////////////////////////////////////////////////////////////////// // class @@ -36,7 +48,9 @@ class CCallsign public: // contructors CCallsign(); - CCallsign(const char *, uint32_t = 0, uint16_t = 0); + CCallsign(const UCallsign &cs); // no id lookup + CCallsign(const CCallsign &cs); + CCallsign(const std::string &cs, uint32_t dmrid = 0, uint16_t nxdnid = 0); // status bool IsValid(void) const; @@ -44,23 +58,24 @@ public: bool HasModule(void) const { return m_Module != ' '; } // set - void SetCallsign(const char *, bool = true); + void SetCallsign(const std::string &s, bool updateids = true); void SetCallsign(const uint8_t *, int, bool = true); void SetDmrid(uint32_t, bool = true); void SetDmrid(const uint8_t *, bool = true); void SetNXDNid(uint16_t, bool = true); void SetNXDNid(const uint8_t *, bool = true); void SetCSModule(char); - void SetSuffix(const char *); + void SetSuffix(const std::string &s); void SetSuffix(const uint8_t *, int); // modify void PatchCallsign(int, const char *, int); // get + UCallsign GetKey() const; void GetCallsign(uint8_t *) const; void GetCallsignString(char *) const; - const std::string GetCS(unsigned len = 9) const; + const std::string GetCS() const; uint32_t GetDmrid(void) const { return m_uiDmrid; } uint16_t GetNXDNid(void) const { return m_uiNXDNid; } void GetSuffix(uint8_t *) const; @@ -73,6 +88,7 @@ public: bool HasSameModule(const CCallsign &) const; // operators + CCallsign &operator = (const CCallsign &cs); bool operator ==(const CCallsign &) const; operator const char *() const; @@ -92,11 +108,10 @@ protected: protected: // data - char m_Callsign[CALLSIGN_LEN]; - char m_Suffix[CALLSUFFIX_LEN]; - char m_Module; - mutable char m_sz[CALLSIGN_LEN+CALLSUFFIX_LEN+5]; - uint32_t m_uiDmrid; - uint16_t m_uiNXDNid; - uint64_t m_coded; // M17 encoded callsign + UCallsign m_Callsign; + USuffix m_Suffix; + char m_Module; + uint32_t m_uiDmrid; + uint16_t m_uiNXDNid; + uint64_t m_coded; // M17 encoded callsign }; diff --git a/reflector/CallsignList.cpp b/reflector/CallsignList.cpp index cb4423b..954bb5a 100644 --- a/reflector/CallsignList.cpp +++ b/reflector/CallsignList.cpp @@ -16,10 +16,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#include #include #include #include -#include "Main.h" + #include "CallsignList.h" //////////////////////////////////////////////////////////////////////////////////////// @@ -27,14 +28,13 @@ CCallsignList::CCallsignList() { - m_Filename = nullptr; memset(&m_LastModTime, 0, sizeof(time_t)); } //////////////////////////////////////////////////////////////////////////////////////// // file io -bool CCallsignList::LoadFromFile(const char *filename) +bool CCallsignList::LoadFromFile(const std::string &filename) { bool ok = false; char sz[256]; @@ -77,7 +77,7 @@ bool CCallsignList::LoadFromFile(const char *filename) file.close(); // keep file path - m_Filename = filename; + m_Filename.assign(filename); // update time GetLastModTime(&m_LastModTime); @@ -99,7 +99,7 @@ bool CCallsignList::ReloadFromFile(void) { bool ok = false; - if ( m_Filename != nullptr ) + if (! m_Filename.empty()) { ok = LoadFromFile(m_Filename); } @@ -210,10 +210,10 @@ bool CCallsignList::GetLastModTime(time_t *time) { bool ok = false; - if ( m_Filename != nullptr ) + if (! m_Filename.empty()) { struct stat fileStat; - if( ::stat(m_Filename, &fileStat) != -1 ) + if( ::stat(m_Filename.c_str(), &fileStat) != -1 ) { *time = fileStat.st_mtime; ok = true; diff --git a/reflector/CallsignList.h b/reflector/CallsignList.h index 8ed2c9c..3a221a1 100644 --- a/reflector/CallsignList.h +++ b/reflector/CallsignList.h @@ -17,7 +17,10 @@ // along with this program. If not, see . #pragma once -#include "Main.h" + +#include +#include + #include "CallsignListItem.h" //////////////////////////////////////////////////////////////////////////////////////// @@ -29,15 +32,12 @@ public: // constructor CCallsignList(); - // destructor - virtual ~CCallsignList() {} - // locks - void Lock(void) { m_Mutex.lock(); } - void Unlock(void) { m_Mutex.unlock(); } + void Lock(void) const { m_Mutex.lock(); } + void Unlock(void) const { m_Mutex.unlock(); } // file io - virtual bool LoadFromFile(const char *); + virtual bool LoadFromFile(const std::string &str); bool ReloadFromFile(void); bool NeedReload(void); @@ -60,8 +60,8 @@ protected: char *TrimWhiteSpaces(char *); // data - std::mutex m_Mutex; - const char * m_Filename; - time_t m_LastModTime; + mutable std::mutex m_Mutex; + std::string m_Filename; + time_t m_LastModTime; std::list m_Callsigns; }; diff --git a/reflector/CallsignListItem.cpp b/reflector/CallsignListItem.cpp index ac0034c..40328bf 100644 --- a/reflector/CallsignListItem.cpp +++ b/reflector/CallsignListItem.cpp @@ -17,7 +17,9 @@ // along with this program. If not, see . #include -#include "Main.h" + +#include "Global.h" + #include "CallsignListItem.h" //////////////////////////////////////////////////////////////////////////////////////// @@ -31,6 +33,7 @@ CCallsignListItem::CCallsignListItem() CCallsignListItem::CCallsignListItem(const CCallsign &callsign, const CIp &ip, const char *modules) { + const std::string mods(g_Configure.GetString(g_Keys.modules.modules)); m_Callsign = callsign; memset(m_szUrl, 0, sizeof(m_szUrl)); m_Ip = ip; @@ -39,14 +42,14 @@ CCallsignListItem::CCallsignListItem(const CCallsign &callsign, const CIp &ip, c memset(m_Modules, 0, sizeof(m_Modules)); if ( modules[0] == '*' ) { - memcpy(m_Modules, ACTIVE_MODULES, sizeof(ACTIVE_MODULES)); + memcpy(m_Modules, mods.c_str(), mods.size()); } else { int n = MIN(::strlen(modules), sizeof(m_Modules)-1); for (int i=0, j=0; i. -#include "Main.h" + #include #include "Client.h" @@ -96,21 +96,14 @@ void CClient::WriteXml(std::ofstream &xmlFile) xmlFile << "" << std::endl; } -void CClient::GetJsonObject(char *Buffer) +void CClient::JsonReport(nlohmann::json &report) { - char sz[512]; - char mbstr[100]; - char cs[16]; - - if (std::strftime(mbstr, sizeof(mbstr), "%A %c", std::localtime(&m_LastHeardTime))) - { - m_Callsign.GetCallsignString(cs); - - ::sprintf(sz, "{\"callsign\":\"%s\",\"module\":\"%c\",\"linkedto\":\"%c\",\"time\":\"%s\"}", - cs, - m_Callsign.GetCSModule(), - m_ReflectorModule, - mbstr); - ::strcat(Buffer, sz); - } + nlohmann::json jclient; + jclient["Callsign"] = m_Callsign.GetCS(); + jclient["OnModule"] = std::string(1, m_ReflectorModule); + jclient["Protocol"] = GetProtocolName(); + char s[100]; + if (std::strftime(s, sizeof(s), "%FT%TZ", std::gmtime(&m_ConnectTime))) + jclient["ConnectTime"] = s; + report["Clients"].push_back(jclient); } diff --git a/reflector/Client.h b/reflector/Client.h index 678e271..48fb022 100644 --- a/reflector/Client.h +++ b/reflector/Client.h @@ -18,6 +18,9 @@ #pragma once +#include + +#include "Defines.h" #include "Timer.h" #include "IP.h" #include "Callsign.h" @@ -44,13 +47,13 @@ public: const CCallsign &GetCallsign(void) const { return m_Callsign; } const CIp &GetIp(void) const { return m_Ip; } bool HasModule(void) const { return m_Callsign.HasModule(); } - char GetCSModule(void) const { return m_Callsign.GetCSModule(); } + char GetCSModule(void) const { return m_Callsign.GetCSModule(); } bool HasReflectorModule(void) const { return m_ReflectorModule != ' '; } char GetReflectorModule(void) const { return m_ReflectorModule; } // set - void SetCSModule(char c) { m_Callsign.SetCSModule(c); } - void SetReflectorModule(char c) { m_ReflectorModule = c; } + void SetCSModule(char c) { m_Callsign.SetCSModule(c); } + void SetReflectorModule(char c) { m_ReflectorModule = c; } // identity virtual EProtocol GetProtocol(void) const { return EProtocol::none; } @@ -71,7 +74,7 @@ public: // reporting virtual void WriteXml(std::ofstream &); - virtual void GetJsonObject(char *); + void JsonReport(nlohmann::json &report); protected: // data diff --git a/reflector/Clients.cpp b/reflector/Clients.cpp index e058070..fcddd63 100644 --- a/reflector/Clients.cpp +++ b/reflector/Clients.cpp @@ -16,15 +16,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" -#include "Reflector.h" -#include "Clients.h" +#include "Global.h" +#include "Clients.h" //////////////////////////////////////////////////////////////////////////////////////// // constructor - CClients::CClients() { } diff --git a/reflector/CodecStream.cpp b/reflector/CodecStream.cpp index 3d7aa1e..afd7334 100644 --- a/reflector/CodecStream.cpp +++ b/reflector/CodecStream.cpp @@ -16,29 +16,18 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include -#include "CodecStream.h" #include "DVFramePacket.h" -#include "Reflector.h" +#include "PacketStream.h" +#include "CodecStream.h" //////////////////////////////////////////////////////////////////////////////////////// // constructor -CCodecStream::CCodecStream(CPacketStream *PacketStream, uint16_t streamid, ECodecType type, std::shared_ptr reader) +CCodecStream::CCodecStream(CPacketStream *PacketStream, char module) : m_CSModule(module), m_IsOpen(false) { - keep_running = true; - m_uiStreamId = streamid; - m_uiPid = 0; - m_eCodecIn = type; - m_RTMin = -1; - m_RTMax = -1; - m_RTSum = 0; - m_RTCount = 0; - m_uiTotalPackets = 0; m_PacketStream = PacketStream; - m_TCReader = reader; - InitCodecStream(); } //////////////////////////////////////////////////////////////////////////////////////// @@ -52,7 +41,27 @@ CCodecStream::~CCodecStream() { m_Future.get(); } + // and close the socket + m_TCReader.Close(); +} + +void CCodecStream::ResetStats(uint16_t streamid, ECodecType type) +{ + m_IsOpen = true; + keep_running = true; + m_uiStreamId = streamid; + m_uiPid = 0; + m_eCodecIn = type; + m_RTMin = -1; + m_RTMax = -1; + m_RTSum = 0; + m_RTCount = 0; + m_uiTotalPackets = 0; +} +void CCodecStream::ReportStats() +{ + m_IsOpen = false; // display stats if (m_RTCount > 0) { @@ -61,7 +70,7 @@ CCodecStream::~CCodecStream() double ave = 1000.0 * m_RTSum / double(m_RTCount); auto prec = std::cout.precision(); std::cout.precision(1); - std::cout << std::fixed << "TC round-trip time(ms): " << min << "/" << ave << "/" << max << ", " << m_RTCount << " total packets" << std::endl; + std::cout << std::fixed << "TC round-trip time(ms): " << min << '/' << ave << '/' << max << ", " << m_RTCount << " total packets" << std::endl; std::cout.precision(prec); } } @@ -69,11 +78,26 @@ CCodecStream::~CCodecStream() //////////////////////////////////////////////////////////////////////////////////////// // initialization -void CCodecStream::InitCodecStream(void) +bool CCodecStream::InitCodecStream() { m_TCWriter.SetUp(REF2TC); + std::string name(TC2REF); + name.append(1, m_CSModule); + if (m_TCReader.Open(name.c_str())) + return true; + std::cout << "Initialized CodecStream receive socket " << name << std::endl; keep_running = true; - m_Future = std::async(std::launch::async, &CCodecStream::Thread, this); + try + { + m_Future = std::async(std::launch::async, &CCodecStream::Thread, this); + } + catch(const std::exception& e) + { + std::cerr << "Could not start Codec processing on module '" << m_CSModule << "': " << e.what() << std::endl; + m_TCReader.Close(); + return true; + } + return false; } //////////////////////////////////////////////////////////////////////////////////////// @@ -92,36 +116,38 @@ void CCodecStream::Task(void) STCPacket pack; // any packet from transcoder - if (m_TCReader->Receive(&pack, 5)) + if (m_TCReader.Receive(&pack, 5)) { // update statistics double rt = pack.rt_timer.time(); // the round-trip time - if ( m_RTMin == -1 ) + if (0 == m_RTCount) { m_RTMin = rt; m_RTMax = rt; - } else { - m_RTMin = MIN(m_RTMin, rt); - m_RTMax = MAX(m_RTMax, rt); - + if (rt < m_RTMin) + m_RTMin = rt; + else if (rt > m_RTMax) + m_RTMax = rt; } m_RTSum += rt; m_RTCount++; - if ( m_LocalQueue.empty() ) + if ( m_LocalQueue.IsEmpty() ) { - std::cout << "Unexpected transcoded packet received from transcoder" << std::endl; + std::cout << "Unexpected transcoded packet received from transcoder: Module='" << pack.module << "' StreamID=" << std::hex << std::showbase << ntohs(pack.streamid) << std::endl; } - else + else if (m_IsOpen) { // pop the original packet - auto Packet = m_LocalQueue.pop(); + auto Packet = m_LocalQueue.Pop(); auto Frame = (CDvFramePacket *)Packet.get(); // do things look okay? + if (pack.module != m_CSModule) + std::cerr << "CodecStream '" << m_CSModule << "' received a transcoded packet from module '" << pack.module << "'" << std::dec << std::noshowbase << std::endl; if (pack.sequence != Frame->GetCodecPacket()->sequence) std::cerr << "Sequence mismatch: this voice frame=" << Frame->GetCodecPacket()->sequence << " returned transcoder packet=" << pack.sequence << std::endl; if (pack.streamid != Frame->GetCodecPacket()->streamid) @@ -137,31 +163,35 @@ void CCodecStream::Task(void) } // and push it back to client - m_PacketStream->Lock(); - m_PacketStream->push(Packet); - m_PacketStream->Unlock(); + m_PacketStream->ReturnPacket(std::move(Packet)); + } + else + { + std::cout << "Transcoder packet received but CodecStream[" << m_CSModule << "] is closed: Module='" << pack.module << "' StreamID=" << std::hex << std::showbase << ntohs(pack.streamid) << std::endl; } } // anything in our queue - while ( !empty() ) + auto Packet = m_Queue.Pop(); + while (Packet) { - // yes, pop it from queue - auto Packet = pop(); - // we need a CDvFramePacket pointer to access Frame stuff auto Frame = (CDvFramePacket *)Packet.get(); - // push to our local queue so it can wait for the transcoder - m_LocalQueue.push(Packet); + if (m_IsOpen) + { + // update important stuff in Frame->m_TCPack for the transcoder + // sets the packet counter, stream id, last_packet, module and start the trip timer + Frame->SetTCParams(m_uiTotalPackets++); - // update important stuff in Frame->m_TCPack for the transcoder - Frame->SetTCParams(m_uiTotalPackets++); + // now send to transcoder + m_TCWriter.Send(Frame->GetCodecPacket()); + + // push to our local queue where it can wait for the transcoder + m_LocalQueue.Push(std::move(Packet)); + } - // now send to transcoder - // this assume that thread pushing the Packet - // have verified that the CodecStream is connected - // and that the packet needs transcoding - m_TCWriter.Send(Frame->GetCodecPacket()); + // get the next packet, if there is one + Packet = m_Queue.Pop(); } } diff --git a/reflector/CodecStream.h b/reflector/CodecStream.h index 04e706f..74db476 100644 --- a/reflector/CodecStream.h +++ b/reflector/CodecStream.h @@ -18,19 +18,27 @@ #pragma once +#include +#include + +#include "DVFramePacket.h" #include "UnixDgramSocket.h" -#include "PacketQueue.h" +#include "SafePacketQueue.h" //////////////////////////////////////////////////////////////////////////////////////// // class class CPacketStream; -class CCodecStream : public CPacketQueue +class CCodecStream { public: // constructor - CCodecStream(CPacketStream *packetstream, uint16_t streamid, ECodecType codectype, std::shared_ptr reader); + CCodecStream(CPacketStream *packetstream, char module); + bool InitCodecStream(); + + void ResetStats(uint16_t streamid, ECodecType codectype); + void ReportStats(); // destructor virtual ~CCodecStream(); @@ -42,9 +50,14 @@ public: void Thread(void); void Task(void); + // pass-thru + void Push(std::unique_ptr p) { m_Queue.Push(std::move(p)); } + protected: - // initialization - void InitCodecStream(void); + // identity + const char m_CSModule; + // state + std::atomic m_IsOpen; // data uint16_t m_uiStreamId; uint16_t m_uiPort; @@ -52,12 +65,14 @@ protected: ECodecType m_eCodecIn; // sockets - std::shared_ptr m_TCReader; + CUnixDgramReader m_TCReader; CUnixDgramWriter m_TCWriter; // associated packet stream CPacketStream *m_PacketStream; - CPacketQueue m_LocalQueue; + + // queues + CSafePacketQueue> m_LocalQueue, m_Queue; // thread std::atomic keep_running; diff --git a/reflector/Configure.cpp b/reflector/Configure.cpp new file mode 100644 index 0000000..35dfa3a --- /dev/null +++ b/reflector/Configure.cpp @@ -0,0 +1,988 @@ +/* + * Copyright (c) 2023 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 "Global.h" +#include "CurlGet.h" + +// ini file keywords +#define JAUTOLINKMODULE "AutoLinkModule" +#define JBLACKLISTPATH "BlacklistPath" +#define JBRANDMEISTER "Brandmeister" +#define JCALLSIGN "Callsign" +#define JCOUNTRY "Country" +#define JDCS "DCS" +#define JDEFAULTID "DefaultId" +#define JDEFAULTRXFREQ "DefaultRxFreq" +#define JDEFAULTTXFREQ "DefaultTxFreq" +#define JDESCRIPTION "Description" +#define JDEXTRA "DExtra" +#define JDMRIDDB "DMR ID DB" +#define JDMRPLUS "DMRPlus" +#define JDPLUS "DPlus" +#define JENABLE "Enable" +#define JFILES "Files" +#define JFILEPATH "FilePath" +#define JG3 "G3" +#define JG3TERMINALPATH "G3TerminalPath" +#define JINTERLINKPATH "InterlinkPath" +#define JIPADDRESS "IPAddress" +#define JIPADDRESSES "IP Addresses" +#define JIPV4BINDING "IPv4Binding" +#define JIPV4EXTERNAL "IPv4External" +#define JIPV6BINDING "IPv6Binding" +#define JIPV6EXTERNAL "IPv6External" +#define JJSONPATH "JsonPath" +#define JM17 "M17" +#define JMMDVM "MMDVM" +#define JMODE "Mode" +#define JMODULE "Module" +#define JMODULES "Modules" +#define JNAMES "Names" +#define JNXDNIDDB "NXDN ID DB" +#define JNXDN "NXDN" +#define JP25 "P25" +#define JPIDPATH "PidPath" +#define JPORT "Port" +#define JREFLECTORID "ReflectorID" +#define JREFRESHMIN "RefreshMin" +#define JREGISTRATIONDESCRIPTION "RegistrationDescription" +#define JREGISTRATIONID "RegistrationID" +#define JREGISTRATIONNAME "RegistrationName" +#define JRXPORT "RxPort" +#define JSPONSOR "Sponsor" +#define JSYSOPEMAIL "SysopEmail" +#define JTRANSCODED "Transcoded" +#define JTRANSCODER "Transcoder" +#define JTXPORT "TxPort" +#define JURF "URF" +#define JURL "URL" +#define JUSRP "USRP" +#define JWHITELISTPATH "WhitelistPath" +#define JXMLPATH "XmlPath" +#define JYSF "YSF" +#define JYSFTXRXDB "YSF TX/RX DB" + +static inline void split(const std::string &s, char delim, std::vector &v) +{ + std::istringstream iss(s); + std::string item; + while (std::getline(iss, item, delim)) + v.push_back(item); +} + +// trim from start (in place) +static inline void ltrim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { + return !std::isspace(ch); + })); +} + +// trim from end (in place) +static inline void rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { + return !std::isspace(ch); + }).base(), s.end()); +} + +// trim from both ends (in place) +static inline void trim(std::string &s) { + ltrim(s); + rtrim(s); +} + +CConfigure::CConfigure() +{ + IPv4RegEx = std::regex("^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3,3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]){1,1}$", std::regex::extended); + IPv6RegEx = std::regex("^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}(:[0-9a-fA-F]{1,4}){1,1}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|([0-9a-fA-F]{1,4}:){1,1}(:[0-9a-fA-F]{1,4}){1,6}|:((:[0-9a-fA-F]{1,4}){1,7}|:))$", std::regex::extended); +} + +bool CConfigure::ReadData(const std::string &path) +// returns true on failure +{ + bool rval = false; + ESection section = ESection::none; + counter = 0; + SJsonKeys::DB *pdb; + + //data.ysfalmodule = 0; + //data.DPlusPort = data.DCSPort = data.DExtraPort = data.BMPort = data.DMRPlusPort = 0; + std::ifstream cfgfile(path.c_str(), std::ifstream::in); + if (! cfgfile.is_open()) { + std::cerr << "ERROR: '" << path << "' was not found!" << std::endl; + return true; + } + + std::string ipv4, ipv6; + + { + CCurlGet curl; + std::stringstream ss; + if (CURLE_OK == curl.GetURL("https://ipv4.icanhazip.com", ss)) + { + ipv4.assign(ss.str()); + trim(ipv4); + } + ss.str(std::string()); + if (CURLE_OK == curl.GetURL("https://ipv6.icanhazip.com", ss)) + { + ipv6.assign(ss.str()); + trim(ipv6); + } + } + + std::string line; + while (std::getline(cfgfile, line)) + { + counter++; + trim(line); + if (3 > line.size()) + continue; // can't be anything + if ('#' == line.at(0)) + continue; // skip comments + + // check for next section + if ('[' == line.at(0)) + { + std::string hname(line.substr(1)); + auto pos = hname.find(']'); + if (std::string::npos != pos) + hname.resize(pos); + section = ESection::none; + if (0 == hname.compare(JNAMES)) + section = ESection::names; + else if (0 == hname.compare(JIPADDRESSES)) + section = ESection::ip; + else if (0 == hname.compare(JMODULES)) + section = ESection::modules; + else if (0 == hname.compare(JDPLUS)) + section = ESection::dplus; + else if (0 == hname.compare(JDEXTRA)) + section = ESection::dextra; + else if (0 == hname.compare(JG3)) + section = ESection::g3; + else if (0 == hname.compare(JDMRPLUS)) + section = ESection::dmrplus; + else if (0 == hname.compare(JMMDVM)) + section = ESection::mmdvm; + else if (0 == hname.compare(JNXDN)) + section = ESection::nxdn; + else if (0 == hname.compare(JBRANDMEISTER)) + section = ESection::bm; + else if (0 == hname.compare(JYSF)) + section = ESection::ysf; + else if (0 == hname.compare(JDCS)) + section = ESection::dcs; + else if (0 == hname.compare(JP25)) + section = ESection::p25; + else if (0 == hname.compare(JM17)) + section = ESection::m17; + else if (0 == hname.compare(JUSRP)) + section = ESection::usrp; + else if (0 == hname.compare(JURF)) + section = ESection::urf; + else if (0 == hname.compare(JDMRIDDB)) + section = ESection::dmrid; + else if (0 == hname.compare(JNXDNIDDB)) + section = ESection::nxdnid; + else if (0 == hname.compare(JYSFTXRXDB)) + section = ESection::ysffreq; + else if (0 == hname.compare(JFILES)) + section = ESection::files; + else + { + std::cerr << "WARNING: unknown ini file section: " << line << std::endl; + } + continue; + } + + std::vector tokens; + split(line, '=', tokens); + // check value for end-of-line comment + if (2 > tokens.size()) + { + std::cout << "WARNING: line #" << counter << ": '" << line << "' does not contain an equal sign, skipping" << std::endl; + continue; + } + auto pos = tokens[1].find('#'); + if (std::string::npos != pos) + { + tokens[1].assign(tokens[1].substr(0, pos)); + rtrim(tokens[1]); // whitespace between the value and the end-of-line comment + } + // trim whitespace from around the '=' + rtrim(tokens[0]); + ltrim(tokens[1]); + const std::string key(tokens[0]); + const std::string value(tokens[1]); + if (key.empty() || value.empty()) + { + std::cout << "WARNING: line #" << counter << ": missing key or value: '" << line << "'" << std::endl; + continue; + } + switch (section) + { + case ESection::names: + if (0 == key.compare(JCALLSIGN)) + data[g_Keys.names.callsign] = value; + else if (0 == key.compare(JSYSOPEMAIL)) + data[g_Keys.names.email] = value; + else if (0 == key.compare(JCOUNTRY)) + data[g_Keys.names.country] = value.substr(0,2); + else if (0 == key.compare(JSPONSOR)) + data[g_Keys.names.sponsor] = value; + else + badParam(key); + break; + case ESection::ip: + if (0 == key.compare(JIPV4BINDING)) + { + data[g_Keys.ip.ipv4bind] = value; + } + else if (0 == key.compare(JIPV6BINDING)) + { + data[g_Keys.ip.ipv6bind] = value; + } + else if (0 == key.compare(JIPV4EXTERNAL)) + { + data[g_Keys.ip.ipv4address] = value; + } + else if (0 == key.compare(JIPV6EXTERNAL)) + { + data[g_Keys.ip.ipv6address] = value; + } + else if (0 == key.compare(JTRANSCODER)) + { + if (value.compare("local")) + { + std::cout << "WARNING: Line #" << counter << ": malformed transcoder address, '" << value << "', resetting..." << std::endl; + } + data[g_Keys.ip.transcoder] = "local"; + } + else + badParam(key); + break; + case ESection::modules: + if (0 == key.compare(JMODULES)) + { + std::string m(value); + if (checkModules(m)) + { + std::cerr << "ERROR: line #" << counter << ": no letters found in Modules: '" << m << "'" << std::endl; + rval = true; + } else + data[g_Keys.modules.modules] = m; + } + else if (0 == key.compare(JTRANSCODED)) + { + std::string m(value); + if (checkModules(m)) + { + std::cerr << "ERROR: line #" << counter << ": no letters found in Transcoded: '" << m << "'" << std::endl; + rval = true; + } else + data[g_Keys.modules.tcmodules] = m; + } + else if (0 == key.compare(0, 11, "Description")) + { + if (12 == key.size() && isupper(key[11])) + data[key] = value; + else + badParam(key); + } + else + badParam(key); + break; + case ESection::bm: + if (0 == key.compare(JPORT)) + data[g_Keys.bm.port] = getUnsigned(value, "Brandmeister Port", 1024, 65535, 10002); + else if (0 == key.compare(JENABLE)) + data[g_Keys.bm.enable] = IS_TRUE(value[0]); + else + badParam(key); + break; + case ESection::dcs: + if (0 == key.compare(JPORT)) + data[g_Keys.dcs.port] = getUnsigned(value, "DCS Port", 1024, 65535, 30051); + else + badParam(key); + break; + case ESection::dextra: + if (0 == key.compare(JPORT)) + data[g_Keys.dextra.port] = getUnsigned(value, "DExtra Port", 1024, 65535, 30001); + else + badParam(key); + break; + case ESection::g3: + if (0 == key.compare(JENABLE)) + data[g_Keys.g3.enable] = IS_TRUE(value[0]); + else + badParam(key); + break; + case ESection::dmrplus: + if (0 == key.compare(JPORT)) + data[g_Keys.dmrplus.port] = getUnsigned(value, "DMRPlus Port", 1024, 65535, 8880); + else + badParam(key); + break; + case ESection::dplus: + if (0 == key.compare(JPORT)) + data[g_Keys.dplus.port] = getUnsigned(value, "DPlus Port", 1024, 65535, 20001); + else + badParam(key); + break; + case ESection::m17: + if (0 == key.compare(JPORT)) + data[g_Keys.m17.port] = getUnsigned(value, "M17 Port", 1024, 65535, 17000); + else + badParam(key); + break; + case ESection::mmdvm: + if (0 == key.compare(JPORT)) + data[g_Keys.mmdvm.port] = getUnsigned(value, "MMDVM Port", 1024, 65535, 62030); + else if (0 == key.compare(JDEFAULTID)) + data[g_Keys.mmdvm.defaultid] = getUnsigned(value, "MMDVM DefaultID", 0, 9999999, 0); + else + badParam(key); + break; + case ESection::nxdn: + if (0 == key.compare(JPORT)) + data[g_Keys.nxdn.port] = getUnsigned(value, "NDXN Port", 1024, 65535, 41400); + else if (0 == key.compare(JAUTOLINKMODULE)) + setAutolink(JNXDN, g_Keys.nxdn.autolinkmod, value); + else if (0 == key.compare(JREFLECTORID)) + data[g_Keys.nxdn.reflectorid] = getUnsigned(value, "NXDN ReflectorID", 0, 65535, 0); + else + badParam(key); + break; + case ESection::p25: + if (0 == key.compare(JPORT)) + data[g_Keys.p25.port] = getUnsigned(value, "P25 Port", 1024, 65535, 41000); + else if (0 == key.compare(JAUTOLINKMODULE)) + setAutolink(JP25, g_Keys.p25.autolinkmod, value); + else if (0 == key.compare(JREFLECTORID)) + data[g_Keys.p25.reflectorid] = getUnsigned(value, "P25 ReflectorID", 0, 16777215, 0); + else + badParam(key); + break; + case ESection::urf: + if (0 == key.compare(JPORT)) + data[g_Keys.urf.port] = getUnsigned(value, "URF Port", 1024, 65535, 10017); + else + badParam(key); + break; + case ESection::usrp: + if (0 == key.compare(JENABLE)) + data[g_Keys.usrp.enable] = IS_TRUE(value[0]); + else if (0 == key.compare(JIPADDRESS)) + data[g_Keys.usrp.ip] = value; + else if (0 == key.compare(JTXPORT)) + data[g_Keys.usrp.txport] = getUnsigned(value, "USRP TxPort", 1024, 65535, 32000); + else if (0 == key.compare(JRXPORT)) + data[g_Keys.usrp.rxport] = getUnsigned(value, "USRP RxPort", 1024, 65535, 34000); + else if (0 == key.compare(JMODULE)) + data[g_Keys.usrp.module] = value.substr(0, 1); + else if (0 == key.compare(JCALLSIGN)) + data[g_Keys.usrp.callsign] = value; + else if (0 == key.compare(JFILEPATH)) + data[g_Keys.usrp.filepath] = value; + else + badParam(key); + break; + case ESection::ysf: + if (0 == key.compare(JPORT)) + data[g_Keys.ysf.port] = getUnsigned(value, "YSF Port", 1024, 65535, 42000); + else if (0 == key.compare(JAUTOLINKMODULE)) + setAutolink(JYSF, g_Keys.ysf.autolinkmod, value); + else if (0 == key.compare(JDEFAULTTXFREQ)) + data[g_Keys.ysf.defaulttxfreq] = getUnsigned(value, "YSF DefaultTxFreq", 40000000, 2600000000, 439000000); + else if (0 == key.compare(JDEFAULTRXFREQ)) + data[g_Keys.ysf.defaultrxfreq] = getUnsigned(value, "YSF DefaultRxFreq", 40000000, 2600000000, 439000000); + else if (0 == key.compare(JREGISTRATIONID)) + data[g_Keys.ysf.ysfreflectordb.id] = getUnsigned(value, "YSF RegistrationID", 0, 9999999, 0); + else if (0 == key.compare(JREGISTRATIONNAME)) + { + std::string name(value); + if (name.size() > 13) name.resize(13); + data[g_Keys.ysf.ysfreflectordb.name] = name; + } + else if (0 == key.compare(JREGISTRATIONDESCRIPTION)) + { + std::string desc(value); + if (desc.size() > 16) desc.resize(16); + data[g_Keys.ysf.ysfreflectordb.description] = desc; + } + else + badParam(key); + break; + case ESection::dmrid: + case ESection::nxdnid: + case ESection::ysffreq: + switch (section) + { + case ESection::dmrid: pdb = &g_Keys.dmriddb; break; + case ESection::nxdnid: pdb = &g_Keys.nxdniddb; break; + case ESection::ysffreq: pdb = &g_Keys.ysftxrxdb; break; + } + if (0 == key.compare(JURL)) + data[pdb->url] = value; + else if (0 == key.compare(JMODE)) + { + if ((0==value.compare("file")) || (0==value.compare("http")) || (0==value.compare("both"))) + data[pdb->mode] = value; + else + { + std::cout << "WARNING: line #" << counter << ": Mode, '" << value << "' not recognized. Setting to 'http'" << std::endl; + data[pdb->mode] = "http"; + } + } + else if (0 == key.compare(JREFRESHMIN)) + data[pdb->refreshmin] = getUnsigned(value, JREFRESHMIN, 15, 14400, 180); + else if (0 == key.compare(JFILEPATH)) + data[pdb->filepath] = value; + else + badParam(key); + break; + case ESection::files: + if (0 == key.compare(JPIDPATH)) + data[g_Keys.files.pid] = value; + else if (0 == key.compare(JXMLPATH)) + data[g_Keys.files.xml] = value; + else if (0 == key.compare(JJSONPATH)) + data[g_Keys.files.json] = value; + else if (0 == key.compare(JWHITELISTPATH)) + data[g_Keys.files.white] = value; + else if (0 == key.compare(JBLACKLISTPATH)) + data[g_Keys.files.black] = value; + else if (0 == key.compare(JINTERLINKPATH)) + data[g_Keys.files.interlink] = value; + else if (0 == key.compare(JG3TERMINALPATH)) + data[g_Keys.files.terminal] = value; + else + badParam(key); + break; + default: + std::cout << "WARNING: parameter '" << line << "' defined befor any [section]" << std::endl; + } + + } + cfgfile.close(); + + ////////////////////////////// check the input + // Names section + if (isDefined(ErrorLevel::fatal, JNAMES, JCALLSIGN, g_Keys.names.callsign, rval)) + { + const auto cs = data[g_Keys.names.callsign].get(); + auto RefRegEx = std::regex("^URF([A-Z0-9]){3,3}$", std::regex::extended); + if (! std::regex_match(cs, RefRegEx)) + { + std::cerr << "ERROR: [" << JNAMES << ']' << JCALLSIGN << " '" << cs << "' is malformed" << std::endl; + rval = true; + } + } + + isDefined(ErrorLevel::mild, JNAMES, JSYSOPEMAIL, g_Keys.names.email, rval); + isDefined(ErrorLevel::mild, JNAMES, JCOUNTRY, g_Keys.names.country, rval); + isDefined(ErrorLevel::mild, JNAMES, JSPONSOR, g_Keys.names.sponsor, rval); + + // IP Address section + // ipv4 bind and external + if (isDefined(ErrorLevel::fatal, JIPADDRESSES, JIPV4BINDING, g_Keys.ip.ipv4bind, rval)) + { + if (std::regex_match(data[g_Keys.ip.ipv4bind].get(), IPv4RegEx)) + { + if (data.contains(g_Keys.ip.ipv4address)) + { + auto v4 = data[g_Keys.ip.ipv4address].get(); + if (std::regex_match(v4, IPv4RegEx)) + { + if (ipv4.compare(v4)) + std::cout << "WARNING: specified IPv4 external address, " << v4 << ", is different than detected address, " << ipv4 << std::endl; + } + else + { + std::cerr << "ERROR: specifed IPv4 external address, " << v4 << ", is malformed" << std::endl; + rval = true; + } + } + else + { + // make sure curl worked! + if (std::regex_match(ipv4, IPv4RegEx)) + data[g_Keys.ip.ipv4address] = ipv4; + else + { + std::cerr << "ERROR: could not detect IPv4 address at this time" << std::endl; + rval = true; + } + } + } + else + { + std::cerr << "ERROR: IPv4 binding address, " << data[g_Keys.ip.ipv4address].get() << ", is malformed" << std::endl; + rval = true; + } + } + + // ipv6 bind and external + if (data.contains(g_Keys.ip.ipv6bind)) + { + if (std::regex_match(data[g_Keys.ip.ipv6bind].get(), IPv6RegEx)) + { + if (data.contains(g_Keys.ip.ipv6address)) + { + auto v6 = data[g_Keys.ip.ipv6address].get(); + if (std::regex_match(v6, IPv6RegEx)) + { + if (ipv6.compare(v6)) + std::cout << "WARNING: specified IPv6 external address [" << v6 << "], is different than detected address [" << ipv6 << ']' << std::endl; + } + else + { + std::cerr << "ERROR: the specifed IPv6 address [" << v6 << "] is malformed" << std::endl; + rval = true; + } + } + else + { + // make sure curl worked! + if (std::regex_match(ipv6, IPv6RegEx)) + data[g_Keys.ip.ipv6address] = ipv6; + else + { + std::cerr << "ERROR: could not detect IPv6 address at this time" << std::endl; + rval = true; + } + } + } + else + { + std::cerr << "ERROR: IPv6 binding address, " << data[g_Keys.ip.ipv6address].get() << ", is malformed" << std::endl; + rval = true; + } + } + else + { + data[g_Keys.ip.ipv6bind] = nullptr; + data[g_Keys.ip.ipv6address] = nullptr; + } + + // Modules section + if (isDefined(ErrorLevel::fatal, JMODULES, JMODULES, g_Keys.modules.modules, rval)) + { + const auto mods(data[g_Keys.modules.modules].get()); + if (data.contains(g_Keys.modules.tcmodules)) + { + const auto tcmods(data[g_Keys.modules.tcmodules].get()); + + // how many transcoded modules + auto size = tcmods.size(); + if (3 != size && 1 != size) + std::cout << "WARNING: [" << JMODULES << ']' << JTRANSCODED << " doesn't define one (or three) modules" << std::endl; + + // make sure each transcoded module is configured + for (auto c : tcmods) + { + if (std::string::npos == mods.find(c)) + { + std::cerr << "ERROR: transcoded module '" << c << "' not found in defined modules" << std::endl; + rval = true; + } + } + } + else + data[g_Keys.modules.tcmodules] = nullptr; + + // finally, check the module descriptions + for (unsigned i=0; i<26; i++) + { + if (std::string::npos == mods.find('A'+i)) + { + if (data.contains(g_Keys.modules.descriptor[i])) + { + std::cout << "WARNING: " << g_Keys.modules.descriptor[i] << " defined for an unconfigured module. Deleting..." << std::endl; + data.erase(g_Keys.modules.descriptor[i]); + } + } + else + { + if (! data.contains(g_Keys.modules.descriptor[i])) + { + std::string value("Module "); + value.append(1, 'A'+i); + std::cout << "WARNING: " << g_Keys.modules.descriptor[i] << " not found. Setting to " << value << std::endl; + data[g_Keys.modules.descriptor[i]] = value; + } + } + } + } + + // "simple" protocols with only a Port + isDefined(ErrorLevel::fatal, JDCS, JPORT, g_Keys.dcs.port, rval); + isDefined(ErrorLevel::fatal, JDEXTRA, JPORT, g_Keys.dextra.port, rval); + isDefined(ErrorLevel::fatal, JDMRPLUS, JPORT, g_Keys.dmrplus.port, rval); + isDefined(ErrorLevel::fatal, JDPLUS, JPORT, g_Keys.dplus.port, rval); + isDefined(ErrorLevel::fatal, JM17, JPORT, g_Keys.m17.port, rval); + isDefined(ErrorLevel::fatal, JURF, JPORT, g_Keys.urf.port, rval); + + // BM + if (isDefined(ErrorLevel::fatal, JBRANDMEISTER, JENABLE, g_Keys.bm.enable, rval)) + { + if (GetBoolean(g_Keys.bm.enable)) + { + isDefined(ErrorLevel::fatal, JBRANDMEISTER, JPORT, g_Keys.bm.port, rval); + } + } + + // G3 + isDefined(ErrorLevel::fatal, JG3, JENABLE, g_Keys.g3.enable, rval); + + // MMDVM + isDefined(ErrorLevel::fatal, JMMDVM, JPORT, g_Keys.mmdvm.port, rval); + isDefined(ErrorLevel::fatal, JMMDVM, JDEFAULTID, g_Keys.mmdvm.defaultid, rval); + + // NXDN + isDefined(ErrorLevel::fatal, JNXDN, JPORT, g_Keys.nxdn.port, rval); + checkAutoLink(JNXDN, JAUTOLINKMODULE, g_Keys.nxdn.autolinkmod, rval); + isDefined(ErrorLevel::fatal, JNXDN, JREFLECTORID, g_Keys.nxdn.reflectorid, rval); + + // P25 + isDefined(ErrorLevel::fatal, JP25, JPORT, g_Keys.p25.port, rval); + checkAutoLink(JP25, JAUTOLINKMODULE, g_Keys.p25.autolinkmod, rval); + isDefined(ErrorLevel::fatal, JP25, JREFLECTORID, g_Keys.p25.reflectorid, rval); + + // USRP + if (isDefined(ErrorLevel::fatal, JUSRP, JENABLE, g_Keys.usrp.enable, rval)) + { + if (GetBoolean(g_Keys.usrp.enable)) + { + if (IsString(g_Keys.modules.tcmodules)) + { + if (isDefined(ErrorLevel::fatal, JUSRP, JMODULE, g_Keys.usrp.module, rval)) + { + if (std::string::npos == GetString(g_Keys.modules.tcmodules).find(GetString(g_Keys.usrp.module).at(0))) + { + std::cerr << "ERROR: [" << JUSRP << ']' << JMODULE << " is not a transcoded module" << std::endl; + rval = true; + } + } + if (isDefined(ErrorLevel::fatal, JUSRP, JIPADDRESS, g_Keys.usrp.ip, rval)) + { + // check for syntax + if (! std::regex_match(data[g_Keys.usrp.ip].get(), IPv4RegEx)) + { + std::cerr << "ERROR: [" << JUSRP << ']' << JIPADDRESS " '" << data[g_Keys.usrp.ip] << "' is malformed" << std::endl; + rval = true; + } + } + isDefined(ErrorLevel::fatal, JUSRP, JTXPORT, g_Keys.usrp.txport, rval); + isDefined(ErrorLevel::fatal, JUSRP, JRXPORT, g_Keys.usrp.rxport, rval); + isDefined(ErrorLevel::fatal, JUSRP, JCALLSIGN, g_Keys.usrp.callsign, rval); + //if (isDefined(ErrorLevel::fatal, JUSRP, JFILEPATH, g_Keys.usrp.filepath, rval)) + if (data.contains(g_Keys.usrp.filepath)) + checkFile(JUSRP, JFILEPATH, data[g_Keys.usrp.filepath]); + } + else + { + std::cerr << "ERROR: " << JUSRP << " requires a transoder" << std::endl; + rval = true; + } + } + } + + // YSF + isDefined(ErrorLevel::fatal, JYSF, JPORT, g_Keys.ysf.port, rval); + checkAutoLink(JYSF, JAUTOLINKMODULE, g_Keys.ysf.autolinkmod, rval); + isDefined(ErrorLevel::fatal, JYSF, JDEFAULTRXFREQ, g_Keys.ysf.defaultrxfreq, rval); + isDefined(ErrorLevel::fatal, JYSF, JDEFAULTTXFREQ, g_Keys.ysf.defaulttxfreq, rval); + isDefined(ErrorLevel::mild, JYSF, JREGISTRATIONID, g_Keys.ysf.ysfreflectordb.id, rval); + isDefined(ErrorLevel::mild, JYSF, JREGISTRATIONNAME, g_Keys.ysf.ysfreflectordb.name, rval); + isDefined(ErrorLevel::mild, JYSF, JREGISTRATIONDESCRIPTION, g_Keys.ysf.ysfreflectordb.description, rval); + + // Databases + std::list> dbs = { + { JDMRIDDB, &g_Keys.dmriddb }, + { JNXDNIDDB, &g_Keys.nxdniddb }, + { JYSFTXRXDB, &g_Keys.ysftxrxdb } + }; + for ( auto &item : dbs ) + { + if (isDefined(ErrorLevel::fatal, item.first, JMODE, item.second->mode, rval)) + { + if (ERefreshType::file != GetRefreshType(item.second->mode)) + { + isDefined(ErrorLevel::fatal, item.first, JURL, item.second->url, rval); + isDefined(ErrorLevel::fatal, item.first, JREFRESHMIN, item.second->refreshmin, rval); + } + if (ERefreshType::http != GetRefreshType(item.second->mode)) + { + if (isDefined(ErrorLevel::fatal, item.first, JFILEPATH, item.second->filepath, rval)) + checkFile(item.first, JFILEPATH, data[item.second->filepath]); + } + } + } + + // Other files + isDefined(ErrorLevel::fatal, JFILES, JPIDPATH, g_Keys.files.pid, rval); + isDefined(ErrorLevel::fatal, JFILES, JXMLPATH, g_Keys.files.xml, rval); + if (isDefined(ErrorLevel::fatal, JFILES, JWHITELISTPATH, g_Keys.files.white, rval)) + checkFile(JFILES, JWHITELISTPATH, data[g_Keys.files.white]); + if (isDefined(ErrorLevel::fatal, JFILES, JBLACKLISTPATH, g_Keys.files.black, rval)) + checkFile(JFILES, JBLACKLISTPATH, data[g_Keys.files.black]); + if (isDefined(ErrorLevel::fatal, JFILES, JINTERLINKPATH, g_Keys.files.interlink, rval)) + checkFile(JFILES, JINTERLINKPATH, data[g_Keys.files.interlink]); + if (data.contains(g_Keys.g3.enable) && GetBoolean(g_Keys.g3.enable)) + { + if (isDefined(ErrorLevel::fatal, JFILES, JG3TERMINALPATH, g_Keys.files.terminal, rval)) + checkFile(JFILES, JG3TERMINALPATH, data[g_Keys.files.terminal]); + } + + return rval; +} + +bool CConfigure::isDefined(ErrorLevel level, const std::string §ion, const std::string &pname, const std::string &key, bool &rval) +{ + if (data.contains(key)) + return true; + + if (ErrorLevel::mild == level) + { + std::cout << "WARNING: [" << section << ']' << pname << " is not defined" << std::endl; + data[key] = nullptr; + } + else + { + std::cerr << "ERROR: [" << section << ']' << pname << " is not defined" << std::endl; + rval = true; + } + return false; +} + +void CConfigure::checkAutoLink(const std::string §ion, const std::string &pname, const std::string &key, bool &rval) +{ + if (data.contains(key)) + { + auto ismods = data.contains(g_Keys.modules.modules); + const auto mods(ismods ? data[g_Keys.modules.modules].get() : ""); + const auto c = data[key].get().at(0); + if (std::string::npos == mods.find(c)) + { + std::cerr << "ERROR: [" << section << ']' << pname << " module '" << c << "' not a configured module" << std::endl; + rval = true; + } + } + else + data[key] = nullptr; +} + +std::string CConfigure::getDataRefreshType(ERefreshType type) const +{ + if (ERefreshType::both == type) + return std::string("both"); + else if (ERefreshType::file == type) + return std::string("file"); + else + return std::string("http"); +} + +unsigned CConfigure::getUnsigned(const std::string &valuestr, const std::string &label, unsigned min, unsigned max, unsigned def) const +{ + auto i = unsigned(std::stoul(valuestr.c_str())); + if ( i < min || i > max ) + { + std::cout << "WARNING: line #" << counter << ": " << label << " is out of range. Reset to " << def << std::endl; + i = def; + } + return (unsigned)i; +} + +void CConfigure::badParam(const std::string &key) const +{ + std::cout << "WARNING: line #" << counter << ": Unexpected parameter: '" << key << "'" << std::endl; +} + +bool CConfigure::checkModules(std::string &m) const +{ + bool rval = false; // return true on error + for(unsigned i=0; i(); + if (0 == s.compare("both")) + type = ERefreshType::both; + else if (0 == s.compare("file")) + type = ERefreshType::file; + else + type = ERefreshType::http; + } + } + return type; +} + +bool CConfigure::Contains(const std::string &key) const +{ + return data.contains(key); +} + +std::string CConfigure::GetString(const std::string &key) const +{ + std::string str; + if (data.contains(key)) + { + if (data[key].is_null()) + { + // null is the same thing as an empty string + return str; + } + else if (data[key].is_string()) + { + str.assign(data[key].get()); + } + else + std::cerr << "ERROR: GetString(): '" << key << "' is not a string" << std::endl; + } + else + { + std::cerr << "ERROR: GetString(): item at '" << key << "' is not defined" << std::endl; + } + return str; +} + +unsigned CConfigure::GetUnsigned(const std::string &key) const +{ + unsigned u = 0; + if (data.contains(key)) + { + if (data[key].is_number_unsigned()) + { + u = data[key].get(); + } + else + std::cerr << "ERROR: GetUnsigned(): '" << key << "' is not an unsigned value" << std::endl; + } + else + { + std::cerr << "ERROR: GetUnsigned(): item at '" << key << "' is not defined" << std::endl; + } + return u; +} + +bool CConfigure::GetBoolean(const std::string &key) const +{ + if (data[key].is_boolean()) + return data[key]; + else + return false; +} + +char CConfigure::GetAutolinkModule(const std::string &key) const +{ + char c = 0; + if (data.contains(key)) + { + if (data[key].is_string()) + { + c = data[key].get().at(0); + } + } + return c; +} + +bool CConfigure::IsString(const std::string &key) const +{ + if (data.contains(key)) + return data[key].is_string(); + return false; +} + +#ifdef INICHECK +SJsonKeys g_Keys; +int main(int argc, char *argv[]) +{ + if (3 == argc && strlen(argv[1]) > 1 && '-' == argv[1][0]) + { + CConfigure d; + auto rval = d.ReadData(argv[2]); + if ('q' != argv[1][1]) + d.Dump(('n' == argv[1][1]) ? true : false); + return rval ? EXIT_FAILURE : EXIT_SUCCESS; + } + std::cerr << "Usage: " << argv[0] << " -(q|n|v) FILENAME\nWhere:\n\t-q just prints warnings and errors.\n\t-n also prints keys that begin with an uppercase letter.\n\t-v prints all keys, warnings and errors." << std::endl; + return EXIT_SUCCESS; +} +#endif diff --git a/reflector/Configure.h b/reflector/Configure.h new file mode 100644 index 0000000..18e6161 --- /dev/null +++ b/reflector/Configure.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 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 + +enum class ErrorLevel { fatal, mild }; +enum class ERefreshType { file, http, both }; +enum class ESection { none, names, ip, modules, urf, dplus, dextra, dcs, g3, dmrplus, mmdvm, nxdn, bm, ysf, p25, m17, usrp, dmrid, nxdnid, ysffreq, files }; + +#define IS_TRUE(a) ((a)=='t' || (a)=='T' || (a)=='1') + +class CConfigure +{ +public: + CConfigure(); + bool ReadData(const std::string &path); + bool Contains(const std::string &key) const; + void Dump(bool justpublic) const; + std::string GetString(const std::string &key) const; + unsigned GetUnsigned(const std::string &key) const; + bool GetBoolean(const std::string &key) const; + ERefreshType GetRefreshType(const std::string &key) const; + bool IsString(const std::string &key) const; + char GetAutolinkModule(const std::string &key) const; + const nlohmann::json &GetData() { return data; } + +private: + // CFGDATA data; + unsigned counter; + nlohmann::json data; + std::regex IPv4RegEx, IPv6RegEx; + + std::string getDataRefreshType(ERefreshType t) const; + unsigned getUnsigned(const std::string &value, const std::string &label, unsigned min, unsigned max, unsigned defaultvalue) const; + void badParam(const std::string ¶m) const; + bool checkModules(std::string &m) const; + void checkFile(const std::string §ion, const std::string &key, const std::string &filepath) const; + void setAutolink(const std::string §ion, const std::string &key, const std::string &value); + bool isDefined(ErrorLevel level, const std::string §ion, const std::string &pname, const std::string &key, bool &rval); + void checkAutoLink(const std::string §ion, const std::string &pname, const std::string &key, bool &rval); +}; diff --git a/reflector/CurlGet.cpp b/reflector/CurlGet.cpp new file mode 100644 index 0000000..5b11794 --- /dev/null +++ b/reflector/CurlGet.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023 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 "CurlGet.h" + +CCurlGet::CCurlGet() +{ + curl_global_init(CURL_GLOBAL_ALL); +} + +CCurlGet::~CCurlGet() +{ + curl_global_cleanup(); +} + +// callback function writes data to a std::ostream +size_t CCurlGet::data_write(void* buf, size_t size, size_t nmemb, void* userp) +{ + if(userp) + { + std::ostream& os = *static_cast(userp); + std::streamsize len = size * nmemb; + if(os.write(static_cast(buf), len)) + return len; + } + + return 0; +} + +CURLcode CCurlGet::GetURL(const std::string &url, std::stringstream &ss, long timeout) +{ + CURLcode code(CURLE_FAILED_INIT); + CURL* curl = curl_easy_init(); + + if(curl) + { + if(CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &data_write)) + && CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L)) + && CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L)) + && CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_FILE, &ss)) + && CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout)) + && CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_URL, url.c_str()))) + { + code = curl_easy_perform(curl); + } + curl_easy_cleanup(curl); + } + if (code != CURLE_OK) + { + std::cout << "ERROR: was not able retrieve data at '" << url << "'\nCurl returned: " << code << std::endl; + } + return code; +} diff --git a/reflector/CurlGet.h b/reflector/CurlGet.h new file mode 100644 index 0000000..29aff4e --- /dev/null +++ b/reflector/CurlGet.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 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 + +class CCurlGet +{ +public: + CCurlGet(); + ~CCurlGet(); + // the contents of the URL will be appended to the stringstream. + CURLcode GetURL(const std::string &url, std::stringstream &ss, long timeout = 30); +private: + static size_t data_write(void* buf, size_t size, size_t nmemb, void* userp); +}; diff --git a/reflector/DCSClient.cpp b/reflector/DCSClient.cpp index a30f140..200c04e 100644 --- a/reflector/DCSClient.cpp +++ b/reflector/DCSClient.cpp @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include "DCSClient.h" diff --git a/reflector/DCSClient.h b/reflector/DCSClient.h index 09d00dd..d22cd04 100644 --- a/reflector/DCSClient.h +++ b/reflector/DCSClient.h @@ -18,6 +18,7 @@ #pragma once +#include "Defines.h" #include "Client.h" class CDcsClient : public CClient diff --git a/reflector/DCSProtocol.cpp b/reflector/DCSProtocol.cpp index 8bb54e0..29dc576 100644 --- a/reflector/DCSProtocol.cpp +++ b/reflector/DCSProtocol.cpp @@ -16,12 +16,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" #include + +#include "Global.h" #include "DCSClient.h" #include "DCSProtocol.h" -#include "Reflector.h" -#include "GateKeeper.h" //////////////////////////////////////////////////////////////////////////////////////// // operation @@ -219,11 +218,10 @@ void CDcsProtocol::OnDvHeaderPacketIn(std::unique_ptr &Header, void CDcsProtocol::HandleQueue(void) { - m_Queue.Lock(); - while ( !m_Queue.empty() ) + while (! m_Queue.IsEmpty()) { // get the packet - auto packet = m_Queue.pop(); + auto packet = m_Queue.Pop(); // get our sender's id const auto module = packet->GetPacketModule(); @@ -270,7 +268,6 @@ void CDcsProtocol::HandleQueue(void) } } } - m_Queue.Unlock(); } //////////////////////////////////////////////////////////////////////////////////////// diff --git a/reflector/DCSProtocol.h b/reflector/DCSProtocol.h index d064dc1..49bbfbc 100644 --- a/reflector/DCSProtocol.h +++ b/reflector/DCSProtocol.h @@ -18,6 +18,7 @@ #pragma once +#include "Defines.h" #include "Timer.h" #include "Protocol.h" #include "DVHeaderPacket.h" diff --git a/reflector/DExtraClient.cpp b/reflector/DExtraClient.cpp index 579352b..560ff66 100644 --- a/reflector/DExtraClient.cpp +++ b/reflector/DExtraClient.cpp @@ -16,10 +16,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" #include "DExtraClient.h" - //////////////////////////////////////////////////////////////////////////////////////// // constructors diff --git a/reflector/DExtraClient.h b/reflector/DExtraClient.h index dd6dab1..f91f941 100644 --- a/reflector/DExtraClient.h +++ b/reflector/DExtraClient.h @@ -18,15 +18,9 @@ #pragma once +#include "Defines.h" #include "Client.h" -//////////////////////////////////////////////////////////////////////////////////////// -// define - - -//////////////////////////////////////////////////////////////////////////////////////// -// class - class CDextraClient : public CClient { public: diff --git a/reflector/DExtraPeer.cpp b/reflector/DExtraPeer.cpp deleted file mode 100644 index e8c04a5..0000000 --- a/reflector/DExtraPeer.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// Created by Antony Chazapis (SV9OAN) on 25/2/2018. -// Copyright © 2016 Jean-Luc Deltombe (LX3JL). All rights reserved. - -// urfd -- The universal reflector -// Copyright © 2021 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 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include "Main.h" -#include -#include "Reflector.h" -#include "DExtraPeer.h" -#include "DExtraClient.h" - - -//////////////////////////////////////////////////////////////////////////////////////// -// constructor - - -CDextraPeer::CDextraPeer() -{ -} - -CDextraPeer::CDextraPeer(const CCallsign &callsign, const CIp &ip, const char *modules, const CVersion &version) - : CPeer(callsign, ip, modules, version) -{ - std::cout << "Adding DExtra peer" << std::endl; - - // and construct the DExtra clients - for ( unsigned i = 0; i < ::strlen(modules); i++ ) - { - // create and append to list - m_Clients.push_back(std::make_shared(callsign, ip, modules[i], EProtoRev::ambe)); - } -} - -//////////////////////////////////////////////////////////////////////////////////////// -// status - -bool CDextraPeer::IsAlive(void) const -{ - for ( auto it=cbegin(); it!=cend(); it++ ) - { - if (! (*it)->IsAlive()) - return false; - } - return true; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// revision helper - -int CDextraPeer::GetProtocolRevision(const CVersion &version) -{ - return version.GetMajor(); -} diff --git a/reflector/DExtraPeer.h b/reflector/DExtraPeer.h deleted file mode 100644 index ac6d538..0000000 --- a/reflector/DExtraPeer.h +++ /dev/null @@ -1,42 +0,0 @@ -// Created by Antony Chazapis (SV9OAN) on 25/2/2018. -// Copyright © 2016 Jean-Luc Deltombe (LX3JL). All rights reserved. - -// urfd -- The universal reflector -// Copyright © 2021 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 3 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 . - -#pragma once - -#include "Peer.h" -#include "DExtraClient.h" - -class CDextraPeer : public CPeer -{ -public: - // constructors - CDextraPeer(); - CDextraPeer(const CCallsign &, const CIp &, const char *, const CVersion &); - CDextraPeer(const CDextraPeer &) = delete; - - // status - bool IsAlive(void) const; - - // identity - EProtocol GetProtocol(void) const { return EProtocol::dextra; } - const char *GetProtocolName(void) const { return "DExtra"; } - - // revision helper - static int GetProtocolRevision(const CVersion &); -}; diff --git a/reflector/DExtraProtocol.cpp b/reflector/DExtraProtocol.cpp index 3b55594..88ed7ef 100644 --- a/reflector/DExtraProtocol.cpp +++ b/reflector/DExtraProtocol.cpp @@ -16,14 +16,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include -#include "DExtraPeer.h" + +#include "Global.h" #include "DExtraClient.h" #include "DExtraProtocol.h" -#include "Reflector.h" -#include "GateKeeper.h" - +# //////////////////////////////////////////////////////////////////////////////////////// // operation @@ -36,7 +35,6 @@ bool CDextraProtocol::Initialize(const char *type, const EProtocol ptype, const // update time m_LastKeepaliveTime.start(); - m_LastPeersLinkTime.start(); // done return true; @@ -103,36 +101,13 @@ void CDextraProtocol::Task(void) // valid module ? if ( g_Reflector.IsValidModule(ToLinkModule) ) { - // is this an ack for a link request? - CPeerCallsignList *list = g_GateKeeper.GetPeerList(); - CCallsignListItem *item = list->FindListItem(Callsign); - if ( item != nullptr && Callsign.GetCSModule() == item->GetModules()[1] && ToLinkModule == item->GetModules()[0] ) - { - std::cout << "DExtra ack packet for module " << ToLinkModule << " from " << Callsign << " at " << Ip << std::endl; - - // already connected ? - CPeers *peers = g_Reflector.GetPeers(); - if ( peers->FindPeer(Callsign, Ip, EProtocol::dextra) == nullptr ) - { - // create the new peer - // this also create one client per module - // append the peer to reflector peer list - // this also add all new clients to reflector client list - peers->AddPeer(std::make_shared(Callsign, Ip, std::string(1, ToLinkModule).c_str(), CVersion(2, 0, 0))); - } - g_Reflector.ReleasePeers(); - } - else - { - // acknowledge the request - EncodeConnectAckPacket(&Buffer, ProtRev); - Send(Buffer, Ip); + // acknowledge the request + EncodeConnectAckPacket(&Buffer, ProtRev); + Send(Buffer, Ip); - // create the client and append - g_Reflector.GetClients()->AddClient(std::make_shared(Callsign, Ip, ToLinkModule, ProtRev)); - g_Reflector.ReleaseClients(); - } - g_GateKeeper.ReleasePeerList(); + // create the client and append + g_Reflector.GetClients()->AddClient(std::make_shared(Callsign, Ip, ToLinkModule, ProtRev)); + g_Reflector.ReleaseClients(); } else { @@ -211,16 +186,6 @@ void CDextraProtocol::Task(void) // update time m_LastKeepaliveTime.start(); } - - // peer connections - if ( m_LastPeersLinkTime.time() > DEXTRA_RECONNECT_PERIOD ) - { - // handle remote peers connections - HandlePeerLinks(); - - // update time - m_LastPeersLinkTime.start(); - } } //////////////////////////////////////////////////////////////////////////////////////// @@ -228,11 +193,10 @@ void CDextraProtocol::Task(void) void CDextraProtocol::HandleQueue(void) { - m_Queue.Lock(); - while ( !m_Queue.empty() ) + while (! m_Queue.IsEmpty() ) { // get the packet - auto packet = m_Queue.pop(); + auto packet = m_Queue.Pop(); // encode it CBuffer buffer; @@ -258,7 +222,6 @@ void CDextraProtocol::HandleQueue(void) g_Reflector.ReleaseClients(); } } - m_Queue.Unlock(); } //////////////////////////////////////////////////////////////////////////////////////// @@ -342,58 +305,6 @@ void CDextraProtocol::HandleKeepalives(void) g_Reflector.ReleasePeers(); } -//////////////////////////////////////////////////////////////////////////////////////// -// Peers helpers - -void CDextraProtocol::HandlePeerLinks(void) -{ - CBuffer buffer; - - // get the list of peers - CPeerCallsignList *list = g_GateKeeper.GetPeerList(); - CPeers *peers = g_Reflector.GetPeers(); - - // check if all our connected peers are still listed by gatekeeper - // if not, disconnect - auto pit = peers->begin(); - std::shared_ptrpeer = nullptr; - while ( (peer = peers->FindNextPeer(EProtocol::dextra, pit)) != nullptr ) - { - if ( list->FindListItem(peer->GetCallsign()) == nullptr ) - { - // send disconnect packet - EncodeDisconnectPacket(&buffer, peer->GetReflectorModules()[0]); - Send(buffer, peer->GetIp()); - std::cout << "Sending disconnect packet to XRF peer " << peer->GetCallsign() << " at " << peer->GetIp() << std::endl; - // remove client - peers->RemovePeer(peer); - } - } - - // check if all ours peers listed by gatekeeper are connected - // if not, connect or reconnect - for ( auto it=list->begin(); it!=list->end(); it++ ) - { - if ( !it->GetCallsign().HasSameCallsignWithWildcard(CCallsign("XRF*")) ) - continue; - if ( strlen(it->GetModules()) != 2 ) - continue; - if ( peers->FindPeer(it->GetCallsign(), EProtocol::dextra) == nullptr ) - { - // resolve again peer's IP in case it's a dynamic IP - it->ResolveIp(); - // send connect packet to re-initiate peer link - EncodeConnectPacket(&buffer, it->GetModules()); - Send(buffer, it->GetIp(), DEXTRA_PORT); - std::cout << "Sending connect packet to XRF peer " << it->GetCallsign() << " @ " << it->GetIp() << " for module " << it->GetModules()[1] << " (module " << it->GetModules()[0] << ")" << std::endl; - } - } - - // done - g_Reflector.ReleasePeers(); - g_GateKeeper.ReleasePeerList(); -} - //////////////////////////////////////////////////////////////////////////////////////// // streams helpers diff --git a/reflector/DExtraProtocol.h b/reflector/DExtraProtocol.h index 69f329d..4434ddd 100644 --- a/reflector/DExtraProtocol.h +++ b/reflector/DExtraProtocol.h @@ -18,6 +18,7 @@ #pragma once +#include "Defines.h" #include "Timer.h" #include "SEProtocol.h" #include "DVHeaderPacket.h" @@ -61,7 +62,6 @@ protected: void HandleQueue(void); // keepalive helpers - void HandlePeerLinks(void); void HandleKeepalives(void); // stream helpers @@ -84,8 +84,6 @@ protected: bool EncodeDvHeaderPacket(const CDvHeaderPacket &, CBuffer &) const; bool EncodeDvFramePacket(const CDvFramePacket &, CBuffer &) const; -protected: // time CTimer m_LastKeepaliveTime; - CTimer m_LastPeersLinkTime; }; diff --git a/reflector/DMRIdDir.cpp b/reflector/DMRIdDir.cpp deleted file mode 100644 index 886a840..0000000 --- a/reflector/DMRIdDir.cpp +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. - -// urfd -- The universal reflector -// Copyright © 2021 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 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include -#include "Main.h" -#include "Reflector.h" -#include "DMRIdDir.h" -#include "DMRIdDirFile.h" -#include "DMRIdDirHttp.h" - -//////////////////////////////////////////////////////////////////////////////////////// -// constructor & destructor - -CDmridDir::CDmridDir() -{ - keep_running = true; -} - -CDmridDir::~CDmridDir() -{ - Close(); -} - - -//////////////////////////////////////////////////////////////////////////////////////// -// init & close - -bool CDmridDir::Init(void) -{ - // load content - Reload(); - - // reset run flag - keep_running = true; - - // start thread; - m_Future = std::async(std::launch::async, &CDmridDir::Thread, this); - - return true; -} - -void CDmridDir::Close(void) -{ - keep_running = false; - if ( m_Future.valid() ) - { - m_Future.get(); - } -} - -//////////////////////////////////////////////////////////////////////////////////////// -// thread - -void CDmridDir::Thread() -{ - while (keep_running) - { - // Wait DMRIDDB_REFRESH_RATE minutes - for (int i=0; i<30*DMRIDDB_REFRESH_RATE && keep_running; i++) - std::this_thread::sleep_for(std::chrono::milliseconds(2000)); - - // have lists files changed ? - if ( NeedReload() ) - { - Reload(); - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////// -// Reload - -bool CDmridDir::Reload(void) -{ - CBuffer buffer; - bool ok = false; - - if ( LoadContent(&buffer) ) - { - Lock(); - { - ok = RefreshContent(buffer); - } - Unlock(); - } - return ok; -} - - -//////////////////////////////////////////////////////////////////////////////////////// -// find - -const CCallsign *CDmridDir::FindCallsign(uint32_t dmrid) -{ - auto found = m_CallsignMap.find(dmrid); - if ( found != m_CallsignMap.end() ) - { - return &(found->second); - } - return nullptr; -} - -uint32_t CDmridDir::FindDmrid(const CCallsign &callsign) -{ - auto found = m_DmridMap.find(callsign); - if ( found != m_DmridMap.end() ) - { - return (found->second); - } - return 0; -} - - -//////////////////////////////////////////////////////////////////////////////////////// -// syntax helpers - -bool CDmridDir::IsValidDmrid(const char *sz) -{ - bool ok = false; - size_t n = ::strlen(sz); - if ( (n > 0) && (n <= 8) ) - { - ok = true; - for ( size_t i = 0; (i < n) && ok; i++ ) - { - ok &= ::isdigit(sz[i]); - } - } - return ok; -} diff --git a/reflector/DMRIdDirFile.cpp b/reflector/DMRIdDirFile.cpp deleted file mode 100644 index d378ef5..0000000 --- a/reflector/DMRIdDirFile.cpp +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. - -// urfd -- The universal reflector -// Copyright © 2021 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 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include -#include -#include -#include "Main.h" -#include "DMRIdDirFile.h" - - -#if (DMRIDDB_USE_RLX_SERVER == 0) -CDmridDirFile g_DmridDir; -#endif - -//////////////////////////////////////////////////////////////////////////////////////// -// constructor & destructor - -CDmridDirFile::CDmridDirFile() -{ - memset(&m_LastModTime, 0, sizeof(time_t)); -} - - -//////////////////////////////////////////////////////////////////////////////////////// -// init & close - -bool CDmridDirFile::Init(void) -{ - return CDmridDir::Init(); -} - -//////////////////////////////////////////////////////////////////////////////////////// -// refresh - -bool CDmridDirFile::NeedReload(void) -{ - bool needReload = false; - - time_t time; - if ( GetLastModTime(&time) ) - { - needReload = time != m_LastModTime; - } - return needReload; -} - -bool CDmridDirFile::LoadContent(CBuffer *buffer) -{ - bool ok = false; - std::ifstream file; - std::streampos size; - - // open file - file.open(DMRIDDB_PATH, std::ios::in | std::ios::binary | std::ios::ate); - if ( file.is_open() ) - { - // read file - size = file.tellg(); - if ( size > 0 ) - { - // read file into buffer - buffer->resize((int)size+1); - file.seekg (0, std::ios::beg); - file.read((char *)buffer->data(), (int)size); - - // close file - file.close(); - - // update time - GetLastModTime(&m_LastModTime); - - // done - ok = true; - } - } - - // done - return ok; -} - -bool CDmridDirFile::RefreshContent(const CBuffer &buffer) -{ - bool ok = false; - - // clear directory - m_CallsignMap.clear(); - m_DmridMap.clear(); - - // scan buffer - if ( buffer.size() > 0 ) - { - // crack it - char *ptr1 = (char *)buffer.data(); - char *ptr2; - - // get next line - while ( (ptr2 = ::strchr(ptr1, '\n')) != nullptr ) - { - *ptr2 = 0; - // get items - char *dmrid; - char *callsign; - if ( ((dmrid = ::strtok(ptr1, ";")) != nullptr) && IsValidDmrid(dmrid) ) - { - if ( ((callsign = ::strtok(nullptr, ";")) != nullptr) ) - { - // new entry - uint32_t ui = atoi(dmrid); - CCallsign cs(callsign, ui); - if ( cs.IsValid() ) - { - m_CallsignMap.insert(std::pair(ui, cs)); - m_DmridMap.insert(std::pair(cs,ui)); - } - } - } - // next line - ptr1 = ptr2+1; - } - - // done - ok = true; - } - - // report - std::cout << "Read " << m_DmridMap.size() << " DMR ids from file " << DMRIDDB_PATH << std::endl; - - // done - return ok; -} - - -bool CDmridDirFile::GetLastModTime(time_t *time) -{ - bool ok = false; - - struct stat fileStat; - if( ::stat(DMRIDDB_PATH, &fileStat) != -1 ) - { - *time = fileStat.st_mtime; - ok = true; - } - return ok; -} diff --git a/reflector/DMRIdDirFile.h b/reflector/DMRIdDirFile.h deleted file mode 100644 index e4f4054..0000000 --- a/reflector/DMRIdDirFile.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. - -// urfd -- The universal reflector -// Copyright © 2021 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 3 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 . - -#pragma once - -#include "DMRIdDir.h" - -class CDmridDirFile : public CDmridDir -{ -public: - // constructor - CDmridDirFile(); - - // destructor - ~CDmridDirFile() {} - - // init & close - bool Init(void); - - // refresh - bool LoadContent(CBuffer *); - bool RefreshContent(const CBuffer &); - -protected: - // reload helpers - bool NeedReload(void); - bool GetLastModTime(time_t *); - -protected: - // data - time_t m_LastModTime; -}; diff --git a/reflector/DMRIdDirHttp.cpp b/reflector/DMRIdDirHttp.cpp deleted file mode 100644 index b0b4f36..0000000 --- a/reflector/DMRIdDirHttp.cpp +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. - -// urfd -- The universal reflector -// Copyright © 2021 Thomas A. Early N7TAE -// Copyright © 2021 Doug McLain AD8DP -// -// 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 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include -#include "Main.h" -#include "Reflector.h" -#include "DMRIdDirHttp.h" - -#if (DMRIDDB_USE_RLX_SERVER == 1) -CDmridDirHttp g_DmridDir; -#endif - - - - -//////////////////////////////////////////////////////////////////////////////////////// -// refresh - -bool CDmridDirHttp::LoadContent(CBuffer *buffer) -{ - // get file from xlxapi server - return HttpGet("xlxapi.rlx.lu", "api/exportdmr.php", 80, buffer); -} - -bool CDmridDirHttp::RefreshContent(const CBuffer &buffer) -{ - bool ok = false; - - // clear directory - m_CallsignMap.clear(); - m_DmridMap.clear(); - - // scan file - if ( buffer.size() > 0 ) - { - char *ptr1 = (char *)buffer.data(); - char *ptr2; - // get next line - while ( (ptr2 = ::strchr(ptr1, '\n')) != nullptr ) - { - *ptr2 = 0; - // get items - char *dmrid; - char *callsign; - if ( ((dmrid = ::strtok(ptr1, ";")) != nullptr) && IsValidDmrid(dmrid) ) - { - if ( ((callsign = ::strtok(nullptr, ";")) != nullptr) ) - { - // new entry - uint32_t ui = atoi(dmrid); - CCallsign cs(callsign, ui); - if ( cs.IsValid() ) - { - m_CallsignMap.insert(std::pair(ui, cs)); - m_DmridMap.insert(std::pair(cs,ui)); - } - } - } - // next line - ptr1 = ptr2+1; - } - // done - ok = true; - } - - // report - std::cout << "Read " << m_DmridMap.size() << " DMR ids from xlxapi.rlx.lu database " << std::endl; - - // done - return ok; -} - - -//////////////////////////////////////////////////////////////////////////////////////// -// httpd helpers - -#define DMRID_HTTPGET_SIZEMAX (256) - -bool CDmridDirHttp::HttpGet(const char *hostname, const char *filename, int port, CBuffer *buffer) -{ - bool ok = false; - int sock_id; - - // open socket - if ( (sock_id = ::socket(AF_INET, SOCK_STREAM, 0)) >= 0 ) - { - // get hostname address - struct sockaddr_in servaddr; - struct hostent *hp; - memset(&servaddr,0,sizeof(servaddr)); - if( (hp = gethostbyname(hostname)) != nullptr ) - { - // dns resolved - memcpy((char *)&servaddr.sin_addr.s_addr, (char *)hp->h_addr, hp->h_length); - servaddr.sin_port = htons(port); - servaddr.sin_family = AF_INET; - - // connect - if ( ::connect(sock_id, (struct sockaddr *)&servaddr, sizeof(servaddr)) == 0) - { - // send the GET request - char request[DMRID_HTTPGET_SIZEMAX]; - ::sprintf(request, "GET /%s HTTP/1.0\r\nFrom: %s\r\nUser-Agent: urfd\r\n\r\n", - filename, (const char *)g_Reflector.GetCallsign()); - ::write(sock_id, request, strlen(request)); - - // config receive timeouts - fd_set read_set; - struct timeval timeout; - timeout.tv_sec = 5; - timeout.tv_usec = 0; - FD_ZERO(&read_set); - FD_SET(sock_id, &read_set); - - // get the reply back - buffer->clear(); - bool done = false; - do - { - char buf[1440]; - ssize_t len = 0; - select(sock_id+1, &read_set, nullptr, nullptr, &timeout); - //if ( (ret > 0) || ((ret < 0) && (errno == EINPROGRESS)) ) - //if ( ret >= 0 ) - //{ - usleep(5000); - len = read(sock_id, buf, 1440); - if ( len > 0 ) - { - buffer->Append((uint8_t *)buf, (int)len); - ok = true; - } - //} - done = (len <= 0); - - } - while (!done); - buffer->Append((uint8_t)0); - - // and disconnect - close(sock_id); - } - else - { - std::cout << "Cannot establish connection with host " << hostname << std::endl; - } - } - else - { - std::cout << "Host " << hostname << " not found" << std::endl; - } - - } - else - { - std::cout << "Failed to open wget socket" << std::endl; - } - - // done - return ok; -} diff --git a/reflector/DMRMMDVMClient.cpp b/reflector/DMRMMDVMClient.cpp index 9f8b1f9..0c1cc1b 100644 --- a/reflector/DMRMMDVMClient.cpp +++ b/reflector/DMRMMDVMClient.cpp @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include "DMRMMDVMClient.h" diff --git a/reflector/DMRMMDVMClient.h b/reflector/DMRMMDVMClient.h index d3c7cec..31c7fac 100644 --- a/reflector/DMRMMDVMClient.h +++ b/reflector/DMRMMDVMClient.h @@ -18,6 +18,7 @@ #pragma once +#include "Defines.h" #include "Client.h" class CDmrmmdvmClient : public CClient diff --git a/reflector/DMRMMDVMProtocol.cpp b/reflector/DMRMMDVMProtocol.cpp index a71d2b9..12bbe38 100644 --- a/reflector/DMRMMDVMProtocol.cpp +++ b/reflector/DMRMMDVMProtocol.cpp @@ -16,18 +16,17 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include + +#include "Global.h" #include "DMRMMDVMClient.h" #include "DMRMMDVMProtocol.h" -#include "Reflector.h" -#include "GateKeeper.h" #include "BPTC19696.h" #include "RS129.h" #include "Golay2087.h" #include "QR1676.h" - //////////////////////////////////////////////////////////////////////////////////////// // define @@ -49,6 +48,7 @@ static uint8_t g_DmrSyncMSData[] = { 0x0D,0x5D,0x7F,0x77,0xFD,0x75,0x70 }; bool CDmrmmdvmProtocol::Initialize(const char *type, const EProtocol ptype, const uint16_t port, const bool has_ipv4, const bool has_ipv6) { + m_DefaultId = g_Configure.GetUnsigned(g_Keys.mmdvm.defaultid); // base class if (! CProtocol::Initialize(type, ptype, port, has_ipv4, has_ipv6)) return false; @@ -346,12 +346,10 @@ void CDmrmmdvmProtocol::OnDvHeaderPacketIn(std::unique_ptr &Hea void CDmrmmdvmProtocol::HandleQueue(void) { - - m_Queue.Lock(); - while ( !m_Queue.empty() ) + while (! m_Queue.IsEmpty()) { // get the packet - auto packet = m_Queue.pop(); + auto packet = m_Queue.Pop(); // get our sender's id const auto mod = packet->GetPacketModule(); @@ -419,7 +417,6 @@ void CDmrmmdvmProtocol::HandleQueue(void) g_Reflector.ReleaseClients(); } } - m_Queue.Unlock(); } //////////////////////////////////////////////////////////////////////////////////////// @@ -860,14 +857,14 @@ void CDmrmmdvmProtocol::EncodeMMDVMPacket(const CDvHeaderPacket &Header, const C uint8_t tag[] = { 'D','M','R','D' }; Buffer->Set(tag, sizeof(tag)); uint8_t cs[12]; - + // DMR header // uiSeqId Buffer->Append((uint8_t)seqid); // uiSrcId uint32_t uiSrcId = Header.GetMyCallsign().GetDmrid(); DvFrame0.GetMyCallsign().GetCallsign(cs); - + if(uiSrcId == 0){ uiSrcId = DvFrame0.GetMyCallsign().GetDmrid(); } @@ -878,9 +875,9 @@ void CDmrmmdvmProtocol::EncodeMMDVMPacket(const CDvHeaderPacket &Header, const C uiSrcId = DvFrame2.GetMyCallsign().GetDmrid(); } if(uiSrcId == 0){ - uiSrcId = DMRMMDVM_DEFAULTID; + uiSrcId = m_DefaultId; } - + AppendDmrIdToBuffer(Buffer, uiSrcId); // uiDstId = TG9 uint32_t uiDstId = 9; // ModuleToDmrDestId(Header.GetRpt2Module()); @@ -982,7 +979,7 @@ char CDmrmmdvmProtocol::DmrDstIdToModule(uint32_t tg) const if (tg > 4000 && tg < 4027) { const char mod = 'A' + (tg - 4001U); - if (strchr(ACTIVE_MODULES, mod)) + if (g_Reflector.IsValidModule(mod)) { return mod; } diff --git a/reflector/DMRMMDVMProtocol.h b/reflector/DMRMMDVMProtocol.h index da8086b..17864c7 100644 --- a/reflector/DMRMMDVMProtocol.h +++ b/reflector/DMRMMDVMProtocol.h @@ -18,6 +18,7 @@ #pragma once +#include "Defines.h" #include "Timer.h" #include "Protocol.h" #include "DVHeaderPacket.h" @@ -117,4 +118,7 @@ protected: // for authentication uint32_t m_uiAuthSeed; + + // config data + unsigned m_DefaultId; }; diff --git a/reflector/DMRPlusClient.cpp b/reflector/DMRPlusClient.cpp index d5c709a..7ae4a63 100644 --- a/reflector/DMRPlusClient.cpp +++ b/reflector/DMRPlusClient.cpp @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include "DMRPlusClient.h" diff --git a/reflector/DMRPlusClient.h b/reflector/DMRPlusClient.h index e0e284c..6cf6868 100644 --- a/reflector/DMRPlusClient.h +++ b/reflector/DMRPlusClient.h @@ -18,6 +18,7 @@ #pragma once +#include "Defines.h" #include "Client.h" class CDmrplusClient : public CClient diff --git a/reflector/DMRPlusProtocol.cpp b/reflector/DMRPlusProtocol.cpp index a6bb453..4de295f 100644 --- a/reflector/DMRPlusProtocol.cpp +++ b/reflector/DMRPlusProtocol.cpp @@ -16,13 +16,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include + +#include "Global.h" #include "DMRPlusClient.h" #include "DMRPlusProtocol.h" -#include "Reflector.h" -#include "GateKeeper.h" -#include "DMRIdDir.h" #include "BPTC19696.h" #include "RS129.h" #include "Golay2087.h" @@ -219,12 +218,10 @@ void CDmrplusProtocol::OnDvHeaderPacketIn(std::unique_ptr &Head void CDmrplusProtocol::HandleQueue(void) { - - m_Queue.Lock(); - while ( !m_Queue.empty() ) + while (! m_Queue.IsEmpty()) { // get the packet - auto packet = m_Queue.pop(); + auto packet = m_Queue.Pop(); // get our sender's id const auto mod = packet->GetPacketModule(); @@ -286,7 +283,6 @@ void CDmrplusProtocol::HandleQueue(void) //buffer.DebugDump(g_Reflector.m_DebugFile); } } - m_Queue.Unlock(); } void CDmrplusProtocol::SendBufferToClients(const CBuffer &buffer, uint8_t module) @@ -642,7 +638,7 @@ char CDmrplusProtocol::DmrDstIdToModule(uint32_t tg) const // is it a 4xxx ? if (tg > 4000 && tg < 4027) { char mod = 'A' + (tg - 4001U); - if (strchr(ACTIVE_MODULES, mod)) + if (g_Reflector.IsValidModule(mod)) { return mod; } diff --git a/reflector/DMRPlusProtocol.h b/reflector/DMRPlusProtocol.h index 83113d9..bf0cca9 100644 --- a/reflector/DMRPlusProtocol.h +++ b/reflector/DMRPlusProtocol.h @@ -18,6 +18,7 @@ #pragma once +#include "Defines.h" #include "Timer.h" #include "Protocol.h" #include "DVHeaderPacket.h" diff --git a/reflector/DPlusClient.cpp b/reflector/DPlusClient.cpp index 098b86a..8e62ecd 100644 --- a/reflector/DPlusClient.cpp +++ b/reflector/DPlusClient.cpp @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include "DPlusClient.h" diff --git a/reflector/DPlusClient.h b/reflector/DPlusClient.h index 4c7bb6e..5e68171 100644 --- a/reflector/DPlusClient.h +++ b/reflector/DPlusClient.h @@ -18,6 +18,7 @@ #pragma once +#include "Defines.h" #include "Client.h" class CDplusClient : public CClient diff --git a/reflector/DPlusProtocol.cpp b/reflector/DPlusProtocol.cpp index 1f135dd..14682fe 100644 --- a/reflector/DPlusProtocol.cpp +++ b/reflector/DPlusProtocol.cpp @@ -16,13 +16,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include + +#include "Global.h" #include "DPlusClient.h" #include "DPlusProtocol.h" -#include "Reflector.h" -#include "GateKeeper.h" - //////////////////////////////////////////////////////////////////////////////////////// // operation @@ -229,11 +228,10 @@ void CDplusProtocol::OnDvHeaderPacketIn(std::unique_ptr &Header void CDplusProtocol::HandleQueue(void) { - m_Queue.Lock(); - while ( !m_Queue.empty() ) + while (! m_Queue.IsEmpty()) { // get the packet - auto packet = m_Queue.pop(); + auto packet = m_Queue.Pop(); // get our sender's id const auto mod = packet->GetPacketModule(); @@ -293,7 +291,6 @@ void CDplusProtocol::HandleQueue(void) g_Reflector.ReleaseClients(); } } - m_Queue.Unlock(); } void CDplusProtocol::SendDvHeader(CDvHeaderPacket *packet, CDplusClient *client) diff --git a/reflector/DPlusProtocol.h b/reflector/DPlusProtocol.h index 8b151e5..47a3ff1 100644 --- a/reflector/DPlusProtocol.h +++ b/reflector/DPlusProtocol.h @@ -18,6 +18,7 @@ #pragma once +#include "Defines.h" #include "Timer.h" #include "SEProtocol.h" #include "DVHeaderPacket.h" diff --git a/reflector/DVFramePacket.cpp b/reflector/DVFramePacket.cpp index 9db20b5..0dfc2b1 100644 --- a/reflector/DVFramePacket.cpp +++ b/reflector/DVFramePacket.cpp @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" +#include #include #include "DVFramePacket.h" @@ -167,10 +167,15 @@ CDvFramePacket::CDvFramePacket(const int16_t *usrp, uint16_t streamid, bool isla m_TCPack.codec_in = ECodecType::usrp; } +std::unique_ptr CDvFramePacket::Copy(void) +{ + return std::unique_ptr(new CDvFramePacket(*this)); +} + // Network unsigned int CDvFramePacket::GetNetworkSize() { - return CPacket::GetNetworkSize() + 4 + 3 + 7 + 14 + 9 + 9 + 16; + return CPacket::GetNetworkSize() + 4 + 3 + 7 + 14 + 9 + 9 + 16 + 11 + 320; } CDvFramePacket::CDvFramePacket(const CBuffer &buf) : CPacket(buf) @@ -189,6 +194,8 @@ CDvFramePacket::CDvFramePacket(const CBuffer &buf) : CPacket(buf) memcpy(m_TCPack.dstar, data+off, 9); off += 9; memcpy(m_TCPack.dmr, data+off, 9); off += 9; memcpy(m_TCPack.m17, data+off, 16); off += 16; + memcpy(m_TCPack.p25, data+off, 11); off += 11; + memcpy(m_TCPack.usrp, data+off, 320); SetTCParams(seq); } else @@ -210,15 +217,9 @@ void CDvFramePacket::EncodeInterlinkPacket(CBuffer &buf) const memcpy(data+off, m_Nonce, 14); off += 14; memcpy(data+off, m_TCPack.dstar, 9); off += 9; memcpy(data+off, m_TCPack.dmr, 9); off += 9; - memcpy(data+off, m_TCPack.m17, 16); -} - -//////////////////////////////////////////////////////////////////////////////////////// -// virtual duplication - -std::unique_ptr CDvFramePacket::Duplicate(void) const -{ - return std::unique_ptr(new CDvFramePacket(*this)); + memcpy(data+off, m_TCPack.m17, 16); off += 16; + memcpy(data+off, m_TCPack.p25, 11); off += 11; + memcpy(data+off, m_TCPack.usrp, 320); } //////////////////////////////////////////////////////////////////////////////////////// @@ -265,15 +266,3 @@ void CDvFramePacket::SetTCParams(uint32_t seq) m_TCPack.module = m_cModule; m_TCPack.rt_timer.start(); } - -//////////////////////////////////////////////////////////////////////////////////////// -// operators - -bool CDvFramePacket::operator ==(const CDvFramePacket &DvFrame) const -{ - return ( (memcmp(m_TCPack.dstar, DvFrame.m_TCPack.dstar, 9) == 0) - && (memcmp(m_uiDvData, DvFrame.m_uiDvData, 3) == 0) - && (memcmp(m_TCPack.dmr, DvFrame.m_TCPack.dmr, 9) == 0) - && (memcmp(m_uiDvSync, DvFrame.m_uiDvSync, 7) == 0) - ); -} diff --git a/reflector/DVFramePacket.h b/reflector/DVFramePacket.h index 10e5eed..d4c964a 100644 --- a/reflector/DVFramePacket.h +++ b/reflector/DVFramePacket.h @@ -62,10 +62,8 @@ public: static unsigned int GetNetworkSize(); void EncodeInterlinkPacket(CBuffer &buf) const; - // virtual duplication - std::unique_ptr Duplicate(void) const; - // identity + std::unique_ptr Copy(void); bool IsDvHeader(void) const { return false; } bool IsDvFrame(void) const { return true; } @@ -82,9 +80,6 @@ public: void SetCodecData(const STCPacket *pack); void SetTCParams(uint32_t seq); - // operators - bool operator ==(const CDvFramePacket &) const; - protected: // data (dstar) uint8_t m_uiDvData[3]; diff --git a/reflector/DVHeaderPacket.cpp b/reflector/DVHeaderPacket.cpp index ab47c3b..d037faa 100644 --- a/reflector/DVHeaderPacket.cpp +++ b/reflector/DVHeaderPacket.cpp @@ -16,12 +16,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include -#include -#include "DMRIdDir.h" -#include "DVHeaderPacket.h" +#include +#include "Defines.h" +#include "DVHeaderPacket.h" //////////////////////////////////////////////////////////////////////////////////////// // constructor @@ -90,7 +90,7 @@ CDvHeaderPacket::CDvHeaderPacket(const struct dstar_header *buffer, uint16_t sid m_uiFlag2 = buffer->Flag2; m_uiFlag3 = buffer->Flag3; m_csUR.SetCallsign(buffer->UR, CALLSIGN_LEN); - + if((buffer->RPT1)[7] == 0x20){ char rptr1[8]; memcpy(rptr1, buffer->RPT1, 8); @@ -100,7 +100,7 @@ CDvHeaderPacket::CDvHeaderPacket(const struct dstar_header *buffer, uint16_t sid else{ m_csRPT1.SetCallsign(buffer->RPT1, CALLSIGN_LEN); } - + m_csRPT1.SetCallsign(buffer->RPT1, CALLSIGN_LEN); m_csRPT2.SetCallsign(buffer->RPT2, CALLSIGN_LEN); m_csMY.SetCallsign(buffer->MY, CALLSIGN_LEN); @@ -165,10 +165,7 @@ CDvHeaderPacket::CDvHeaderPacket(const CM17Packet &m17) : CPacket(m17) m_csRPT1.SetCSModule('G'); } -//////////////////////////////////////////////////////////////////////////////////////// -// virtual duplication - -std::unique_ptr CDvHeaderPacket::Duplicate(void) const +std::unique_ptr CDvHeaderPacket::Copy(void) { return std::unique_ptr(new CDvHeaderPacket(*this)); } @@ -204,34 +201,3 @@ bool CDvHeaderPacket::IsValid(void) const return valid; } - - -//////////////////////////////////////////////////////////////////////////////////////// -// operators - -bool CDvHeaderPacket::operator ==(const CDvHeaderPacket &Header) const -{ - return ( (m_uiFlag1 == Header.m_uiFlag1) && - (m_uiFlag2 == Header.m_uiFlag2) && - (m_uiFlag3 == Header.m_uiFlag3) && - (m_csUR == Header.m_csUR) && - (m_csRPT1 == Header.m_csRPT1) && - (m_csRPT2 == Header.m_csRPT2) && - (m_csMY == Header.m_csMY) ); -} - -#ifdef IMPLEMENT_CDVHEADERPACKET_CONST_CHAR_OPERATOR -CDvHeaderPacket::operator const char *() const -{ - char *sz = (char *)(const char *)m_sz; - - std::sprintf(sz, "%02X %02X %02X\n%s\n%s\n%s\n%s", - m_uiFlag1, m_uiFlag2, m_uiFlag3, - (const char *)m_csUR, - (const char *)m_csRPT1, - (const char *)m_csRPT2, - (const char *)m_csMY); - - return m_sz; -} -#endif diff --git a/reflector/DVHeaderPacket.h b/reflector/DVHeaderPacket.h index 2a6a8cd..63793be 100644 --- a/reflector/DVHeaderPacket.h +++ b/reflector/DVHeaderPacket.h @@ -20,7 +20,7 @@ #include "Callsign.h" #include "Packet.h" - + //////////////////////////////////////////////////////////////////////////////////////// // implementation details @@ -65,10 +65,8 @@ public: static unsigned int GetNetworkSize(); void EncodeInterlinkPacket(CBuffer &buf) const; - // virtual duplication - std::unique_ptr Duplicate(void) const; - // identity + std::unique_ptr Copy(void); bool IsDvHeader(void) const { return true; } bool IsDvFrame(void) const { return false; } @@ -94,9 +92,6 @@ public: void SetRpt2Callsign(const CCallsign &cs) { m_csRPT2 = cs; } void SetRpt2Module(char c) { m_csRPT2.SetCSModule(c); } - // operators - bool operator ==(const CDvHeaderPacket &) const; - protected: // data uint8_t m_uiFlag1; diff --git a/reflector/Defines.h b/reflector/Defines.h new file mode 100644 index 0000000..8d28a4a --- /dev/null +++ b/reflector/Defines.h @@ -0,0 +1,143 @@ +// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. + +// urfd -- The universal reflector +// Copyright © 2021 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 3 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 . + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////////////// +// defines + +//// Module configuration +#define DSTAR_IPV4 true +#define DMR_IPV4 true +#define YSF_IPV4 true +#define XLX_IPV4 true +#define M17_IPV4 true +#define P25_IPV4 true +#define NXDN_IPV4 true +#define USRP_IPV4 true +#define URF_IPV4 true + + +#define DSTAR_IPV6 true // QnetGateway can use IPv6 +#define DMR_IPV6 false +#define YSF_IPV6 false +#define XLX_IPV6 false +#define M17_IPV6 true +#define P25_IPV6 false +#define NXDN_IPV6 false +#define USRP_IPV6 false +#define URF_IPV6 true + +// protocols --------------------------------------------------- + +enum class EProtocol { any, none, dextra, dplus, dcs, g3, bm, urf, dmrplus, dmrmmdvm, nxdn, p25, usrp, ysf, m17 }; + +// DExtra +#define DEXTRA_KEEPALIVE_PERIOD 3 // in seconds +#define DEXTRA_KEEPALIVE_TIMEOUT (DEXTRA_KEEPALIVE_PERIOD*10) // in seconds + +// DPlus +#define DPLUS_KEEPALIVE_PERIOD 1 // in seconds +#define DPLUS_KEEPALIVE_TIMEOUT (DPLUS_KEEPALIVE_PERIOD*10) // in seconds +#define DPLUS_DEFAULT_RPTR1_SUFFIX 'Y' + +// DCS +#define DCS_KEEPALIVE_PERIOD 1 // in seconds +#define DCS_KEEPALIVE_TIMEOUT (DCS_KEEPALIVE_PERIOD*30) // in seconds + +// XLX, used for BM +#define BM_KEEPALIVE_PERIOD 1 // in seconds +#define BM_KEEPALIVE_TIMEOUT (BM_KEEPALIVE_PERIOD*30) // in seconds +#define BM_RECONNECT_PERIOD 5 // in seconds + +// URF +#define URF_KEEPALIVE_PERIOD 1 // in seconds +#define URF_KEEPALIVE_TIMEOUT (URF_KEEPALIVE_PERIOD*30) // in seconds +#define URF_RECONNECT_PERIOD 5 // in seconds + +// DMRPlus (dongle) +#define DMRPLUS_KEEPALIVE_PERIOD 1 // in seconds +#define DMRPLUS_KEEPALIVE_TIMEOUT (DMRPLUS_KEEPALIVE_PERIOD*10) // in seconds +#define DMRPLUS_REFLECTOR_SLOT DMR_SLOT2 +#define DMRPLUS_REFLECTOR_COLOUR 1 + +// DMRMmdvm +#define DMRMMDVM_KEEPALIVE_PERIOD 10 // in seconds +#define DMRMMDVM_KEEPALIVE_TIMEOUT (DMRMMDVM_KEEPALIVE_PERIOD*10) // in seconds +#define DMRMMDVM_REFLECTOR_SLOT DMR_SLOT2 +#define DMRMMDVM_REFLECTOR_COLOUR 1 + +// YSF +#define YSF_KEEPALIVE_PERIOD 3 // in seconds +#define YSF_KEEPALIVE_TIMEOUT (YSF_KEEPALIVE_PERIOD*10) // in seconds + +// M17 +#define M17_KEEPALIVE_PERIOD 3 +#define M17_KEEPALIVE_TIMEOUT (M17_KEEPALIVE_PERIOD*10) + +// P25 +#define P25_KEEPALIVE_PERIOD 3 // in seconds +#define P25_KEEPALIVE_TIMEOUT (P25_KEEPALIVE_PERIOD*10) // in seconds + +// NXDN +#define NXDN_KEEPALIVE_PERIOD 3 // in seconds +#define NXDN_KEEPALIVE_TIMEOUT (NXDN_KEEPALIVE_PERIOD*10) // in seconds + +// USRP +#define USRP_KEEPALIVE_PERIOD 1 // in seconds +#define USRP_KEEPALIVE_TIMEOUT (USRP_KEEPALIVE_PERIOD*10) // in seconds + +// G3 Terminal +#define G3_PRESENCE_PORT 12346 // UDP port +#define G3_CONFIG_PORT 12345 // UDP port +#define G3_DV_PORT 40000 // UDP port +#define G3_KEEPALIVE_PERIOD 10 // in seconds +#define G3_KEEPALIVE_TIMEOUT 3600 // in seconds, 1 hour + + +//////////////////////////////////////////////////////////////////////////////////////// +// macros + +#define MIN(a,b) ((a)<(b))?(a):(b) +#define MAKEWORD(low, high) ((uint16_t)(((uint8_t)(low)) | (((uint16_t)((uint8_t)(high))) << 8))) +#define MAKEDWORD(low, high) ((uint32_t)(((uint16_t)(low)) | (((uint32_t)((uint16_t)(high))) << 16))) +#define LOBYTE(w) ((uint8_t)(uint16_t)(w & 0x00FF)) +#define HIBYTE(w) ((uint8_t)((((uint16_t)(w)) >> 8) & 0xFF)) +#define LOWORD(dw) ((uint16_t)(uint32_t)(dw & 0x0000FFFF)) +#define HIWORD(dw) ((uint16_t)((((uint32_t)(dw)) >> 16) & 0xFFFF)) diff --git a/reflector/G3Client.cpp b/reflector/G3Client.cpp index 9b6cbc5..f69e6fc 100644 --- a/reflector/G3Client.cpp +++ b/reflector/G3Client.cpp @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include "G3Client.h" diff --git a/reflector/G3Client.h b/reflector/G3Client.h index d5c313d..07b2454 100644 --- a/reflector/G3Client.h +++ b/reflector/G3Client.h @@ -18,6 +18,7 @@ #pragma once +#include "Defines.h" #include "Client.h" class CG3Client : public CClient diff --git a/reflector/G3Protocol.cpp b/reflector/G3Protocol.cpp index 1368e54..7c15ca8 100644 --- a/reflector/G3Protocol.cpp +++ b/reflector/G3Protocol.cpp @@ -16,16 +16,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" +#include +#include #include #include + +#include "Global.h" #include "G3Client.h" #include "G3Protocol.h" -#include "Reflector.h" -#include "GateKeeper.h" - -#include -#include //////////////////////////////////////////////////////////////////////////////////////// @@ -34,6 +32,10 @@ bool CG3Protocol::Initialize(const char */*type*/, const EProtocol /*type*/, const uint16_t /*port*/, const bool /*has_ipv4*/, const bool /*has_ipv6*/) // everything is hard coded until ICOM gets their act together and start supporting IPv6 { + //config data + m_TerminalPath.assign(g_Configure.GetString(g_Keys.files.terminal)); + const std::string ipv4address(g_Configure.GetString(g_Keys.ip.ipv4bind)); + ReadOptions(); // init reflector apparent callsign @@ -46,7 +48,7 @@ bool CG3Protocol::Initialize(const char */*type*/, const EProtocol /*type*/, con //m_ReflectorCallsign.PatchCallsign(0, "XLX", 3); // create our sockets - CIp ip(AF_INET, G3_DV_PORT, LISTEN_IPV4); + CIp ip(AF_INET, G3_DV_PORT, ipv4address.c_str()); if ( ip.IsSet() ) { if (! m_Socket4.Open(ip)) @@ -440,14 +442,13 @@ void CG3Protocol::Task(void) void CG3Protocol::HandleQueue(void) { - m_Queue.Lock(); - while ( !m_Queue.empty() ) + while (! m_Queue.IsEmpty()) { // supress host checks m_LastKeepaliveTime.start(); // get the packet - auto packet = m_Queue.pop(); + auto packet = m_Queue.Pop(); // encode it CBuffer buffer; @@ -473,7 +474,6 @@ void CG3Protocol::HandleQueue(void) g_Reflector.ReleaseClients(); } } - m_Queue.Unlock(); } //////////////////////////////////////////////////////////////////////////////////////// @@ -672,7 +672,7 @@ void CG3Protocol::NeedReload(void) { struct stat fileStat; - if (::stat(TERMINALOPTIONS_PATH, &fileStat) != -1) + if (::stat(m_TerminalPath.c_str(), &fileStat) != -1) { if (m_LastModTime != fileStat.st_mtime) { @@ -701,7 +701,7 @@ void CG3Protocol::ReadOptions(void) int opts = 0; - std::ifstream file(TERMINALOPTIONS_PATH); + std::ifstream file(m_TerminalPath.c_str()); if (file.is_open()) { m_GwAddress = 0u; @@ -742,12 +742,12 @@ void CG3Protocol::ReadOptions(void) } } } - std::cout << "G3 handler loaded " << opts << " options from file " << TERMINALOPTIONS_PATH << std::endl; + std::cout << "G3 handler loaded " << opts << " options from file " << m_TerminalPath << std::endl; file.close(); struct stat fileStat; - if (::stat(TERMINALOPTIONS_PATH, &fileStat) != -1) + if (::stat(m_TerminalPath.c_str(), &fileStat) != -1) { m_LastModTime = fileStat.st_mtime; } diff --git a/reflector/G3Protocol.h b/reflector/G3Protocol.h index 193e529..d9c6b73 100644 --- a/reflector/G3Protocol.h +++ b/reflector/G3Protocol.h @@ -19,6 +19,8 @@ #pragma once #include + +#include "Defines.h" #include "Timer.h" #include "SEProtocol.h" #include "DVHeaderPacket.h" @@ -117,4 +119,5 @@ protected: uint32_t m_GwAddress; std::string m_Modules; time_t m_LastModTime; + std::string m_TerminalPath; }; diff --git a/reflector/GateKeeper.cpp b/reflector/GateKeeper.cpp index e00043a..8afb9f3 100644 --- a/reflector/GateKeeper.cpp +++ b/reflector/GateKeeper.cpp @@ -16,14 +16,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" -#include "Timer.h" -#include "GateKeeper.h" - -//////////////////////////////////////////////////////////////////////////////////////// - -CGateKeeper g_GateKeeper; +#include "Timer.h" +#include "Global.h" //////////////////////////////////////////////////////////////////////////////////////// // constructor @@ -49,9 +44,9 @@ bool CGateKeeper::Init(void) { // load lists from files - m_NodeWhiteList.LoadFromFile(WHITELIST_PATH); - m_NodeBlackList.LoadFromFile(BLACKLIST_PATH); - m_PeerList.LoadFromFile(INTERLINKLIST_PATH); + m_NodeWhiteList.LoadFromFile(g_Configure.GetString(g_Keys.files.white)); + m_NodeBlackList.LoadFromFile(g_Configure.GetString(g_Keys.files.black)); + m_PeerList.LoadFromFile(g_Configure.GetString(g_Keys.files.interlink)); // reset run flag keep_running = true; @@ -92,9 +87,7 @@ bool CGateKeeper::MayLink(const CCallsign &callsign, const CIp &ip, EProtocol pr case EProtocol::p25: case EProtocol::usrp: case EProtocol::nxdn: -#ifndef NO_G3 case EProtocol::g3: -#endif // first check is IP & callsigned listed OK ok &= IsNodeListedOk(callsign, ip); // todo: then apply any protocol specific authorisation for the operation @@ -141,9 +134,7 @@ bool CGateKeeper::MayTransmit(const CCallsign &callsign, const CIp &ip, const EP case EProtocol::p25: case EProtocol::nxdn: case EProtocol::usrp: -#ifndef NO_G3 case EProtocol::g3: -#endif // first check is IP & callsigned listed OK ok = ok && IsNodeListedOk(callsign, ip, module); // todo: then apply any protocol specific authorisation for the operation @@ -213,17 +204,20 @@ bool CGateKeeper::IsNodeListedOk(const CCallsign &callsign, const CIp &ip, char { // first check if callsign is in white list // note if white list is empty, everybody is authorized - const_cast(m_NodeWhiteList).Lock(); + m_NodeWhiteList.Lock(); if ( !m_NodeWhiteList.empty() ) { ok = m_NodeWhiteList.IsCallsignListedWithWildcard(callsign, module); } - const_cast(m_NodeWhiteList).Unlock(); + m_NodeWhiteList.Unlock(); // then check if not blacklisted - const_cast(m_NodeBlackList).Lock(); - ok &= !m_NodeBlackList.IsCallsignListedWithWildcard(callsign); - const_cast(m_NodeBlackList).Unlock(); + if (ok) + { + m_NodeBlackList.Lock(); + ok = !m_NodeBlackList.IsCallsignListedWithWildcard(callsign); + m_NodeBlackList.Unlock(); + } } // done @@ -241,12 +235,12 @@ bool CGateKeeper::IsPeerListedOk(const CCallsign &callsign, const CIp &ip, char if ( ok ) { // look for an exact match in the list - const_cast(m_PeerList).Lock(); + m_PeerList.Lock(); if ( !m_PeerList.empty() ) { ok = m_PeerList.IsCallsignListed(callsign, module); } - const_cast(m_PeerList).Unlock(); + m_PeerList.Unlock(); } // done @@ -263,12 +257,12 @@ bool CGateKeeper::IsPeerListedOk(const CCallsign &callsign, const CIp &ip, char if ( ok ) { // look for an exact match in the list - const_cast(m_PeerList).Lock(); + m_PeerList.Lock(); if ( !m_PeerList.empty() ) { ok = m_PeerList.IsCallsignListed(callsign, modules); } - const_cast(m_PeerList).Unlock(); + m_PeerList.Unlock(); } // done @@ -300,10 +294,8 @@ const std::string CGateKeeper::ProtocolName(const EProtocol p) const return "USRP"; case EProtocol::bm: return "Brandmeister"; -#ifndef NO_G3 case EProtocol::g3: return "Icom G3"; -#endif default: return "NONE"; } diff --git a/reflector/GateKeeper.h b/reflector/GateKeeper.h index a35316f..4b5814d 100644 --- a/reflector/GateKeeper.h +++ b/reflector/GateKeeper.h @@ -19,7 +19,7 @@ #pragma once -#include "Main.h" +#include "Defines.h" #include "Callsign.h" #include "IP.h" #include "CallsignList.h" diff --git a/reflector/YSFNodeDirHttp.h b/reflector/Global.h similarity index 57% rename from reflector/YSFNodeDirHttp.h rename to reflector/Global.h index c697f91..0a3c43c 100644 --- a/reflector/YSFNodeDirHttp.h +++ b/reflector/Global.h @@ -1,7 +1,5 @@ -// Copyright © 2019 Jean-Luc Deltombe (LX3JL). All rights reserved. - // urfd -- The universal reflector -// Copyright © 2021 Thomas A. Early N7TAE +// Copyright © 2023 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 @@ -16,25 +14,20 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#pragma once - -#include "YSFNodeDir.h" - -class CYsfNodeDirHttp : public CYsfNodeDir -{ -public: - // constructor - CYsfNodeDirHttp() {} - - // destructor - ~CYsfNodeDirHttp() {} - - // refresh - bool LoadContent(CBuffer *); - bool RefreshContent(const CBuffer &); +#include "Reflector.h" +#include "GateKeeper.h" +#include "Configure.h" +#include "Version.h" +#include "LookupDmr.h" +#include "LookupNxdn.h" +#include "LookupYsf.h" +#include "JsonKeys.h" -protected: - // reload helpers - bool NeedReload(void) { return true; } - bool HttpGet(const char *, const char *, int, CBuffer *); -}; +extern CReflector g_Reflector; +extern CGateKeeper g_GateKeeper; +extern CConfigure g_Configure; +extern CVersion g_Version; +extern CLookupDmr g_LDid; +extern CLookupNxdn g_LNid; +extern CLookupYsf g_LYtr; +extern SJsonKeys g_Keys; diff --git a/reflector/JsonKeys.h b/reflector/JsonKeys.h new file mode 100644 index 0000000..f604e30 --- /dev/null +++ b/reflector/JsonKeys.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023 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 + +#pragma once + +// configuration key names +struct SJsonKeys { + struct PORTONLY { const std::string port; } + dcs { "DCSPort" }, + dextra { "DExtraPort" }, + dmrplus { "DMRPlusPort" }, + dplus { "DPlusPort" }, + m17 { "M17Port" }, + urf { "URFPort" }; + + struct G3 { const std::string enable; } + g3 { "G3Enable" }; + + struct BM { const std::string enable, port; } + bm { "bmEnable", "bmPort" }; + + struct MMDVM { const std::string port, defaultid; } + mmdvm { "MMDVMPort", "mmdvmdefaultid" }; + + struct NAMES { const std::string callsign, email, country, sponsor; } + names { "Callsign", "SysopEmail", "Country", "Sponsor" }; + + struct IP { const std::string ipv4bind, ipv4address, ipv6bind, ipv6address, transcoder; } + ip { "ipv4bind", "IPv4Address", "ipv6bind", "IPv6Address", "tcaddress" }; + + struct MODULES { const std::string modules, tcmodules, descriptor[26]; } + modules { "Modules", "TranscodedModules", + "DescriptionA", "DescriptionB", "DescriptionC", "DescriptionD", "DescriptionE", "DescriptionF", "DescriptionG", "DescriptionH", "DescriptionI", "DescriptionJ", "DescriptionK", "DescriptionL", "DescriptionM", "DescriptionN", "DescriptionO", "DescriptionP", "DescriptionQ", "DescriptionR", "DescriptionS", "DescriptionT", "DescriptionU", "DescriptionV", "DescriptionW", "DescriptionX", "DescriptionY", "DescriptionZ" }; + + struct USRP { const std::string enable, ip, txport, rxport, module, callsign, filepath; } + usrp { "usrpEnable", "usrpIpAddress", "urspTxPort", "usrpRxPort", "usrpModule", "usrpCallsign", "usrpFilePath" }; + + struct P25NXDN { const std::string port, autolinkmod, reflectorid; } + p25 { "P25Port", "P25AutolinkMod", "P25ReflectorID" }, + nxdn { "NXDNPort", "NXDNAutolinkMod", "NXDNReflectorID" }; + + struct YSF { const std::string port, autolinkmod, defaulttxfreq, defaultrxfreq; + struct YSLREG { const std::string id, name, description; } ysfreflectordb; } + ysf { "YSFPort", "YSFAutoLinkMod", "YSFDefaultTxFreq", "YSFDefaultRxFreq", + { "ysfrefdbid", "ysfrefdbname", "ysfrefdbdesc" } }; + + struct DB { const std::string url, mode, refreshmin, filepath; } + dmriddb { "dmrIdDbUrl", "dmrIdDbMode", "dmrIdDbRefresh", "dmrIdDbFilePath" }, + nxdniddb { "nxdnIdDbUrl", "nxdnIdDbMode", "nxdnIdDbRefresh", "nxdnIdDbFilePath" }, + ysftxrxdb { "ysfIdDbUrl", "ysfIdDbMode", "ysfIdDbRefresh", "ysfIdDbFilePath" }; + + struct FILES { const std::string pid, xml, json, white, black, interlink, terminal; } + files { "pidFilePath", "xmlFilePath", "jsonFilePath", "whitelistFilePath", "blacklistFilePath", "interlinkFilePath", "g3TerminalFilePath" }; +}; diff --git a/reflector/Lookup.cpp b/reflector/Lookup.cpp new file mode 100644 index 0000000..9d1fbc6 --- /dev/null +++ b/reflector/Lookup.cpp @@ -0,0 +1,125 @@ +// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. + +// urfd -- The universal reflector +// Copyright © 2023 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 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include +#include +#include "CurlGet.h" +#include "Lookup.h" + +void CLookup::LookupClose() +{ + keep_running = false; + if (m_Future.valid()) + m_Future.get(); +} + +std::time_t CLookup::GetLastModTime() +{ + struct stat fileStat; + if(0 == stat(m_Path.c_str(), &fileStat)) + { + return fileStat.st_mtime; + } + return 0; +} + +void CLookup::LookupInit() +{ + LoadParameters(); + + m_Future = std::async(std::launch::async, &CLookup::Thread, this); +} + +void CLookup::Thread() +{ + const unsigned long wait_cycles = m_Refresh * 6u; // the number of while loops in m_Refresh + unsigned long count = 0; + while (keep_running) + { + std::stringstream ss; + bool http_loaded = false; + bool file_loaded = false; + + // load http section first, if configured and m_Refresh minutes have lapsed + // on the first pass through this while loop (count == 0) + if (ERefreshType::file != m_Type && 0ul == count++ % wait_cycles) + { + // if SIG_INT was received at this point in time, + // in might take a bit more than 10 seconds to soft close + http_loaded = LoadContentHttp(ss); + } + + // load the file if http was loaded or if we haven't loaded since the last mod time + if (ERefreshType::http != m_Type) + { + if (http_loaded || m_LastLoadTime < GetLastModTime()) + { + file_loaded = LoadContentFile(ss); + time(&m_LastLoadTime); + } + } + + // now update the map(s) if anything was loaded + if (http_loaded || file_loaded) + { + Lock(); + // if m_Type == ERefreshType::both, and if something was deleted from the file, + // it won't be purged from the map(s) until http is loaded + // It would be a lot of work (iterating on an unordered_map) to do otherwise! + if (http_loaded || ERefreshType::file == m_Type) + ClearContents(); + UpdateContent(ss, Eaction::normal); + Unlock(); + } + + // now wait for 10 seconds + std::this_thread::sleep_for(std::chrono::seconds(10)); + } +} + +bool CLookup::LoadContentHttp(std::stringstream &ss) +{ + CCurlGet get; + auto code = get.GetURL(m_Url, ss); + return CURLE_OK == code; +} + +bool CLookup::LoadContentFile(std::stringstream &ss) +{ + bool rval = false; + std::ifstream file(m_Path); + if ( file ) + { + ss << file.rdbuf(); + file.close(); + rval = true; + } + return rval; +} + +bool CLookup::Utility(Eaction action, Esource source) +{ + std::stringstream ss; + LoadParameters(); + auto rval = (Esource::http == source) ? LoadContentHttp(ss) : LoadContentFile(ss); + if (rval) + UpdateContent(ss, action); + return rval; +} diff --git a/reflector/DMRIdDir.h b/reflector/Lookup.h similarity index 50% rename from reflector/DMRIdDir.h rename to reflector/Lookup.h index e4b8935..e7edd78 100644 --- a/reflector/DMRIdDir.h +++ b/reflector/Lookup.h @@ -1,7 +1,7 @@ // Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. // urfd -- The universal reflector -// Copyright © 2021 Thomas A. Early N7TAE +// Copyright © 2023 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 @@ -18,68 +18,68 @@ #pragma once -#include -#include -#include -#include -#include "Buffer.h" +#include +#include +#include #include "Callsign.h" +#include "Configure.h" + +enum class Eaction { normal, parse, error_only }; +enum class Esource { http, file }; // compare function for std::map::find -struct CDmridDirCallsignCompare +struct CCallsignHash +{ + std::size_t operator() (const UCallsign &ucs) const + { + std::hash hash; + return hash(ucs.l); + } +}; + +struct CCallsignEqual { - bool operator() (const CCallsign &cs1, const CCallsign &cs2) const - { return cs1.HasLowerCallsign(cs2);} + bool operator() (const UCallsign &ucs1, const UCallsign &ucs2) const + { + return ucs1.l == ucs2.l; + } }; //////////////////////////////////////////////////////////////////////////////////////// -class CDmridDir +class CLookup { public: // constructor - CDmridDir(); - - // destructor - ~CDmridDir(); + CLookup() : keep_running(true), m_LastLoadTime(0) {} - // init & close - virtual bool Init(void); - virtual void Close(void); + void LookupInit(); + void LookupClose(); // locks - void Lock(void) { m_Mutex.lock(); } - void Unlock(void) { m_Mutex.unlock(); } - - // refresh - virtual bool LoadContent(CBuffer *) { return false; } - virtual bool RefreshContent(const CBuffer &) { return false; } - - // find - const CCallsign *FindCallsign(uint32_t); - uint32_t FindDmrid(const CCallsign &); + void Lock(void) { m_Mutex.lock(); } + void Unlock(void) { m_Mutex.unlock(); } + bool Utility(Eaction action, Esource source); protected: - // thread + std::time_t GetLastModTime(); + virtual void LoadParameters() = 0; + virtual void ClearContents() = 0; void Thread(); - // reload helpers - bool Reload(void); - virtual bool NeedReload(void) { return false; } - bool IsValidDmrid(const char *); - -protected: - // data - std::map m_CallsignMap; - std::map m_DmridMap; + // refresh + bool LoadContentHttp(std::stringstream &ss); + bool LoadContentFile(std::stringstream &ss); + virtual void UpdateContent(std::stringstream &ss, Eaction action) = 0; - // Lock() std::mutex m_Mutex; + ERefreshType m_Type; + unsigned m_Refresh; + std::string m_Path, m_Url; + std::time_t m_LastLoadTime; - // thread std::atomic keep_running; std::future m_Future; - }; diff --git a/reflector/LookupDmr.cpp b/reflector/LookupDmr.cpp new file mode 100644 index 0000000..04ab0e1 --- /dev/null +++ b/reflector/LookupDmr.cpp @@ -0,0 +1,103 @@ +// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. + +// urfd -- The universal reflector +// Copyright © 2023 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 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include +#include + +#include "Global.h" + +void CLookupDmr::ClearContents() +{ + m_CallsignMap.clear(); + m_DmridMap.clear(); +} + +void CLookupDmr::LoadParameters() +{ + m_Type = g_Configure.GetRefreshType(g_Keys.dmriddb.mode); + m_Refresh = g_Configure.GetUnsigned(g_Keys.dmriddb.refreshmin); + m_Path.assign(g_Configure.GetString(g_Keys.dmriddb.filepath)); + m_Url.assign(g_Configure.GetString(g_Keys.dmriddb.url)); +} + +uint32_t CLookupDmr::FindDmrid(const UCallsign &ucs) const +{ + auto found = m_DmridMap.find(ucs); + if ( found != m_DmridMap.end() ) + { + return (found->second); + } + return 0; +} + +const UCallsign *CLookupDmr::FindCallsign(const uint32_t dmrid) const +{ + auto found = m_CallsignMap.find(dmrid); + if ( found != m_CallsignMap.end() ) + { + return &(found->second); + } + return nullptr; +} + +void CLookupDmr::UpdateContent(std::stringstream &ss, Eaction action) +{ + std::string line; + while (std::getline(ss, line)) + { + bool failed = true; + auto l = atol(line.c_str()); // no throw guarantee + if (0L < l && l <= 9999999L) + { + auto id = uint32_t(l); + auto p1 = line.find(';'); + if (std::string::npos != p1) + { + auto p2 = line.find(';', ++p1); + if (std::string::npos != p2) + { + const auto cs_str(line.substr(p1, p2-p1)); + CCallsign cs; + cs.SetCallsign(cs_str, false); + if (cs.IsValid()) + { + failed = false; + if (Eaction::normal == action) + { + auto key = cs.GetKey(); + m_DmridMap[key] = id; + m_CallsignMap[id] = key; + } + else if (Eaction::parse == action) + { + std::cout << id << ';' << cs_str << ";\n"; + } + } + } + } + } + if (Eaction::error_only == action && failed) + { + std::cout << line << '\n'; + } + } + if (Eaction::normal == action) + std::cout << "DMR Id database size: " << m_DmridMap.size() << std::endl; +} diff --git a/reflector/NXDNIdDirHttp.h b/reflector/LookupDmr.h similarity index 62% rename from reflector/NXDNIdDirHttp.h rename to reflector/LookupDmr.h index c26311c..b5a6d09 100644 --- a/reflector/NXDNIdDirHttp.h +++ b/reflector/LookupDmr.h @@ -1,7 +1,7 @@ // Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. // urfd -- The universal reflector -// Copyright © 2021 Thomas A. Early N7TAE +// Copyright © 2023 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 @@ -18,23 +18,21 @@ #pragma once -#include "NXDNIdDir.h" +#include "Lookup.h" -class CNXDNidDirHttp : public CNXDNidDir +class CLookupDmr : public CLookup { public: - // constructor - CNXDNidDirHttp() {} - - // destructor - ~CNXDNidDirHttp() {} - - // refresh - bool LoadContent(CBuffer *); - bool RefreshContent(const CBuffer &); + ~CLookupDmr() {} + uint32_t FindDmrid(const UCallsign &ucs) const; + const UCallsign *FindCallsign(uint32_t dmrid) const; protected: - // reload helpers - bool NeedReload(void) { return true; } - bool HttpGet(const char *, const char *, int, CBuffer *); + void ClearContents(); + void LoadParameters(); + void UpdateContent(std::stringstream &ss, Eaction action); + +private: + std::unordered_map m_CallsignMap; + std::unordered_map m_DmridMap; }; diff --git a/reflector/LookupNxdn.cpp b/reflector/LookupNxdn.cpp new file mode 100644 index 0000000..7189b8f --- /dev/null +++ b/reflector/LookupNxdn.cpp @@ -0,0 +1,103 @@ +// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. + +// urfd -- The universal reflector +// Copyright © 2023 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 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include +#include + +#include "Global.h" + +void CLookupNxdn::ClearContents() +{ + m_CallsignMap.clear(); + m_NxdnidMap.clear(); +} + +void CLookupNxdn::LoadParameters() +{ + m_Type = g_Configure.GetRefreshType(g_Keys.nxdniddb.mode); + m_Refresh = g_Configure.GetUnsigned(g_Keys.nxdniddb.refreshmin); + m_Path.assign(g_Configure.GetString(g_Keys.nxdniddb.filepath)); + m_Url.assign(g_Configure.GetString(g_Keys.nxdniddb.url)); +} + +const UCallsign *CLookupNxdn::FindCallsign(uint16_t nxdnid) const +{ + auto found = m_CallsignMap.find(nxdnid); + if ( found != m_CallsignMap.end() ) + { + return &(found->second); + } + return nullptr; +} + +uint16_t CLookupNxdn::FindNXDNid(const UCallsign &ucs) const +{ + auto found = m_NxdnidMap.find(ucs); + if ( found != m_NxdnidMap.end() ) + { + return found->second; + } + return 0; +} + +void CLookupNxdn::UpdateContent(std::stringstream &ss, Eaction action) +{ + std::string line; + while (std::getline(ss, line)) + { + bool failed = true; + auto l = atol(line.c_str()); // no throw guarantee + if (0 < l && l < 0x10000) + { + auto id = uint32_t(l); + auto p1 = line.find(','); + if (std::string::npos != p1) + { + auto p2 = line.find(',', ++p1); + if (std::string::npos != p2) + { + const auto cs_str = line.substr(p1, p2-p1); + CCallsign cs; + cs.SetCallsign(cs_str, false); + if (cs.IsValid()) + { + failed = false; + if (Eaction::normal == action) + { + auto key = cs.GetKey(); + m_NxdnidMap[key] = id; + m_CallsignMap[id] = key; + } + else if (Eaction::parse == action) + { + std::cout << id << ',' << cs_str << ",\n"; + } + } + } + } + } + if (Eaction::error_only == action && failed) + { + std::cout << line << '\n'; + } + } + if (Eaction::normal == action) + std::cout << "NXDN Id database size: " << m_NxdnidMap.size() << std::endl; +} diff --git a/reflector/DMRIdDirHttp.h b/reflector/LookupNxdn.h similarity index 63% rename from reflector/DMRIdDirHttp.h rename to reflector/LookupNxdn.h index 54d2577..9310f5e 100644 --- a/reflector/DMRIdDirHttp.h +++ b/reflector/LookupNxdn.h @@ -1,7 +1,7 @@ // Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. // urfd -- The universal reflector -// Copyright © 2021 Thomas A. Early N7TAE +// Copyright © 2023 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 @@ -18,23 +18,19 @@ #pragma once -#include "DMRIdDir.h" +#include "Lookup.h" -class CDmridDirHttp : public CDmridDir +class CLookupNxdn : public CLookup { public: - // constructor - CDmridDirHttp() {} - - // destructor - ~CDmridDirHttp() {} - - // refresh - bool LoadContent(CBuffer *); - bool RefreshContent(const CBuffer &); - + uint16_t FindNXDNid(const UCallsign &ucs) const; + const UCallsign *FindCallsign(const uint16_t id) const; protected: - // reload helpers - bool NeedReload(void) { return true; } - bool HttpGet(const char *, const char *, int, CBuffer *); + void ClearContents(); + void LoadParameters(); + void UpdateContent(std::stringstream &ss, Eaction action); + +private: + std::unordered_map m_CallsignMap; + std::unordered_map m_NxdnidMap; }; diff --git a/reflector/LookupYsf.cpp b/reflector/LookupYsf.cpp new file mode 100644 index 0000000..5e1ca96 --- /dev/null +++ b/reflector/LookupYsf.cpp @@ -0,0 +1,89 @@ +// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. + +// urfd -- The universal reflector +// Copyright © 2023 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 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include +#include + +#include "Global.h" + +void CLookupYsf::ClearContents() +{ + m_map.clear(); +} + +void CLookupYsf::LoadParameters() +{ + m_Type = g_Configure.GetRefreshType(g_Keys.ysftxrxdb.mode); + m_Refresh = g_Configure.GetUnsigned(g_Keys.ysftxrxdb.refreshmin); + m_Path.assign(g_Configure.GetString(g_Keys.ysftxrxdb.filepath)); + m_Url.assign(g_Configure.GetString(g_Keys.ysftxrxdb.url)); + m_DefaultTx = g_Configure.GetUnsigned(g_Keys.ysf.defaulttxfreq); + m_DefaultRx = g_Configure.GetUnsigned(g_Keys.ysf.defaultrxfreq); +} + +void CLookupYsf::UpdateContent(std::stringstream &ss, Eaction action) +{ + std::string line; + while (std::getline(ss, line)) + { + CCallsign cs; + std::string cs_str, tx_str, rx_str; + std::istringstream iss(line); + std::getline(iss, cs_str, ';'); + std::getline(iss, tx_str, ';'); + std::getline(iss, rx_str, ';'); + cs.SetCallsign(cs_str, false); + auto ltx = atoll(tx_str.c_str()); + auto lrx = atoll(rx_str.c_str()); + unsigned x = sizeof(lrx); + if (ltx > 40000000 && ltx < 0x100000000 && lrx > 40000000 && lrx < 0x100000000 && cs.IsValid()) + { + if (Eaction::parse == action) + { + std::cout << cs_str << ';' << tx_str << ';' << rx_str << ";\n"; + } + else if (Eaction::normal == action) + { + m_map[cs.GetKey()] = CYsfNode(ltx, lrx); + } + } + else if (Eaction::error_only == action) + { + std::cout << line << '\n'; + } + } + if (Eaction::normal == action) + std::cout << "YSF frequency database size now is " << m_map.size() << std::endl; +} + +void CLookupYsf::FindFrequencies(const CCallsign &cs, uint32_t &txfreq, uint32_t &rxfreq) +{ + auto found = m_map.find(cs.GetKey()); + if (found != m_map.end()) + { + txfreq = found->second.GetTxFrequency(); + rxfreq = found->second.GetRxFrequency(); + } + else + { + txfreq = m_DefaultTx; + rxfreq = m_DefaultRx; + } +} diff --git a/reflector/NXDNIdDirFile.h b/reflector/LookupYsf.h similarity index 64% rename from reflector/NXDNIdDirFile.h rename to reflector/LookupYsf.h index f4fe8c9..00d9f53 100644 --- a/reflector/NXDNIdDirFile.h +++ b/reflector/LookupYsf.h @@ -1,7 +1,7 @@ // Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. // urfd -- The universal reflector -// Copyright © 2021 Thomas A. Early N7TAE +// Copyright © 2023 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 @@ -18,30 +18,23 @@ #pragma once -#include "NXDNIdDir.h" +#include "YSFNode.h" +#include "Lookup.h" -class CNXDNidDirFile : public CNXDNidDir +using CsNodeMap = std::unordered_map; + +class CLookupYsf : public CLookup { public: - // constructor - CNXDNidDirFile(); - - // destructor - ~CNXDNidDirFile() {} - - // init & close - bool Init(void); - - // refresh - bool LoadContent(CBuffer *); - bool RefreshContent(const CBuffer &); + void FindFrequencies(const CCallsign &, uint32_t &, uint32_t &); protected: - // reload helpers - bool NeedReload(void); - bool GetLastModTime(time_t *); + void ClearContents(); + void LoadParameters(); + void UpdateContent(std::stringstream &ss, Eaction action); -protected: - // data - time_t m_LastModTime; +private: + CsNodeMap m_map; + + unsigned m_DefaultTx, m_DefaultRx; }; diff --git a/reflector/M17Client.cpp b/reflector/M17Client.cpp index c4cb8c4..888e469 100644 --- a/reflector/M17Client.cpp +++ b/reflector/M17Client.cpp @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include "M17Client.h" diff --git a/reflector/M17Client.h b/reflector/M17Client.h index 407090e..2186fa0 100644 --- a/reflector/M17Client.h +++ b/reflector/M17Client.h @@ -16,15 +16,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#include "Defines.h" #include "Client.h" -//////////////////////////////////////////////////////////////////////////////////////// -// define - - -//////////////////////////////////////////////////////////////////////////////////////// -// class - class CM17Client : public CClient { public: diff --git a/reflector/M17Packet.cpp b/reflector/M17Packet.cpp index 94e9510..7ed45a8 100644 --- a/reflector/M17Packet.cpp +++ b/reflector/M17Packet.cpp @@ -84,11 +84,6 @@ void CM17Packet::SetCRC(uint16_t crc) m17.crc = htons(crc); } -std::unique_ptr CM17Packet::Duplicate(void) const -{ - return std::unique_ptr(new CM17Packet(*this)); -} - bool CM17Packet::IsLastPacket() const { return ((0x8000u & ntohs(m17.framenumber)) == 0x8000u); diff --git a/reflector/M17Packet.h b/reflector/M17Packet.h index 2b0fb83..3d58ffd 100644 --- a/reflector/M17Packet.h +++ b/reflector/M17Packet.h @@ -69,7 +69,6 @@ public: uint16_t GetStreamId() const; uint16_t GetCRC() const; void SetCRC(uint16_t crc); - std::unique_ptr Duplicate(void) const; bool IsLastPacket() const; private: diff --git a/reflector/M17Protocol.cpp b/reflector/M17Protocol.cpp index df228f9..2516389 100644 --- a/reflector/M17Protocol.cpp +++ b/reflector/M17Protocol.cpp @@ -16,13 +16,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include #include "M17Client.h" #include "M17Protocol.h" #include "M17Packet.h" -#include "Reflector.h" -#include "GateKeeper.h" +#include "Global.h" //////////////////////////////////////////////////////////////////////////////////////// // operation @@ -222,11 +221,10 @@ void CM17Protocol::OnDvHeaderPacketIn(std::unique_ptr &Header, void CM17Protocol::HandleQueue(void) { - m_Queue.Lock(); - while ( !m_Queue.empty() ) + while (! m_Queue.IsEmpty()) { // get the packet - auto packet = m_Queue.pop(); + auto packet = m_Queue.Pop(); // get our sender's id const auto module = packet->GetPacketModule(); @@ -271,7 +269,6 @@ void CM17Protocol::HandleQueue(void) m_StreamsCache[module].m_iSeqCounter++; } } - m_Queue.Unlock(); } //////////////////////////////////////////////////////////////////////////////////////// diff --git a/reflector/M17Protocol.h b/reflector/M17Protocol.h index 5e9e2c7..61c21ea 100644 --- a/reflector/M17Protocol.h +++ b/reflector/M17Protocol.h @@ -18,6 +18,7 @@ #pragma once +#include "Defines.h" #include "Timer.h" #include "Protocol.h" #include "DVHeaderPacket.h" diff --git a/reflector/Main.cpp b/reflector/Main.cpp index 8a19a5d..098d35a 100644 --- a/reflector/Main.cpp +++ b/reflector/Main.cpp @@ -16,53 +16,56 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" -#include "Reflector.h" - #include +#include "Global.h" +#ifndef UTILITY //////////////////////////////////////////////////////////////////////////////////////// // global objects +SJsonKeys g_Keys; CReflector g_Reflector; +CGateKeeper g_GateKeeper; +CConfigure g_Configure; +CVersion g_Version(3,0,1); // The major byte should only change if the interlink packet changes! +CLookupDmr g_LDid; +CLookupNxdn g_LNid; +CLookupYsf g_LYtr; //////////////////////////////////////////////////////////////////////////////////////// -// function declaration - -#include "Users.h" -int main() +int main(int argc, char *argv[]) { - const std::string cs(CALLSIGN); - if ((cs.size() != 6) || cs.compare(0, 3, "URF")) + if (argc != 2) { - std::cerr << "Malformed reflector callsign: '" << cs << "', aborting!" << std::endl; + std::cerr << "No configuration file specifed! Usage: " << argv[0] << " /pathname/to/configuration/file" << std::endl; return EXIT_FAILURE; } + if (g_Configure.ReadData(argv[1])) + return EXIT_FAILURE; + + std::cout << "IPv4 binding address is '" << g_Configure.GetString(g_Keys.ip.ipv4bind) << "'" << std::endl; // remove pidfile - remove(PIDFILE_PATH); + const std::string pidpath(g_Configure.GetString(g_Keys.files.pid)); + const std::string callsign(g_Configure.GetString(g_Keys.names.callsign)); + remove(pidpath.c_str()); // splash - std::cout << "Starting " << cs << " " << VERSION_MAJOR << "." << VERSION_MINOR << "." << VERSION_REVISION << std::endl << std::endl; - -#ifdef TRANSCODER_IS_REMOTE - g_Reflector.SetTranscoderIp(TRANSCODER_IP, INET6_ADDRSTRLEN); -#endif - + std::cout << "Starting " << callsign << " " << g_Version << std::endl; // and let it run - if ( !g_Reflector.Start() ) + if (g_Reflector.Start()) { std::cout << "Error starting reflector" << std::endl; return EXIT_FAILURE; } - std::cout << "Reflector " << g_Reflector.GetCallsign() << "started and listening" << std::endl; + std::cout << "Reflector " << callsign << " started and listening" << std::endl; // write new pid file - std::ofstream ofs(PIDFILE_PATH, std::ofstream::out); + std::ofstream ofs(pidpath, std::ofstream::out); ofs << getpid() << std::endl; ofs.close(); @@ -74,3 +77,132 @@ int main() // done return EXIT_SUCCESS; } + +#else // UTILITY is defined + +//////////////////////////////////////////////////////////////////////////////////////// +// global objects + +SJsonKeys g_Keys; +CConfigure g_Configure; +CLookupDmr g_LDid; +CLookupNxdn g_LNid; +CLookupYsf g_LYtr; + +static void usage(std::ostream &os, const char *name) +{ + os << "\nUsage: " << name << " DATABASE SOURCE ACTION INIFILE\n"; + os << "DATABASE (choose one)\n" + " dmr : The DmrId <==> Callsign databases.\n" + " nxdn : The NxdnId <==> Callsign databases.\n" + " ysf : The Callsign => Tx/Rx frequency database.\n" + "SOURCE (choose one)\n" + " file : The file specified by the FilePath ini parameter.\n" + " http : The URL specified by the URL ini paramater.\n" + "ACTION (choose one)\n" + " print : Print all lines from the SOURCE that are syntactically correct.\n" + " error : Print only the lines with failed syntax.\n" + "INIFILE : an error-free urfd ini file (check it first with inicheck).\n\n" + "Only the first character of DATABASE, SOURCE and ACTION is read.\n" + "Example: " << name << " y f e urfd.ini # Check your YSF Tx/Rx database file specifed in urfd.ini for syntax errors.\n\n"; +} + +enum class Edb { none, dmr, nxdn, ysf }; + +int main(int argc, char *argv[]) +{ + Edb db; + Eaction action; + Esource source; + + if (5 != argc) + { + usage(std::cerr, argv[0]); + return EXIT_FAILURE; + } + + switch (argv[1][0]) + { + case 'd': + case 'D': + db = Edb::dmr; + break; + case 'n': + case 'N': + db = Edb::nxdn; + break; + + case 'y': + case 'Y': + db = Edb::ysf; + break; + + default: + std::cout << "Unrecognized DATABASE: " << argv[1]; + db = Edb::none; + break; + } + + switch (argv[2][0]) + { + case 'h': + case 'H': + source = Esource::http; + break; + + case 'f': + case 'F': + source = Esource::file; + break; + + default: + std::cerr << "Unrecognized SOURCE: " << argv[2] << std::endl; + db = Edb::none; + break; + } + + switch (argv[3][0]) + { + case 'p': + case 'P': + action = Eaction::parse; + break; + + case 'e': + case 'E': + action = Eaction::error_only; + break; + + default: + std::cerr << "Unrecognized ACTION: " << argv[3] << std::endl; + db = Edb::none; + break; + } + + if (db == Edb::none) + { + usage(std::cerr, argv[0]); + return EXIT_FAILURE; + } + + if (g_Configure.ReadData(argv[4])) + return EXIT_FAILURE; + + switch (db) + { + case Edb::dmr: + g_LDid.Utility(action, source); + break; + + case Edb::nxdn: + g_LNid.Utility(action, source); + break; + + case Edb::ysf: + g_LYtr.Utility(action, source); + break; + } + + return EXIT_SUCCESS; +} +#endif diff --git a/reflector/Makefile b/reflector/Makefile index ea751fa..c9c8272 100644 --- a/reflector/Makefile +++ b/reflector/Makefile @@ -18,78 +18,56 @@ # if you change these locations, make sure the sgs.service file is updated! # you will also break hard coded paths in the dashboard file, index.php. -include configure.mk +EXE = urfd -# if you make changed in these two variable, you'll need to change things -# in the main.h file as well as the systemd service file. -BINDIR = /usr/local/bin -CFGDIR = /usr/local/etc +INICHECK = inicheck -CC = g++ +DBUTIL = dbutil + +include urfd.mk ifeq ($(debug), true) -CFLAGS = -ggdb3 -W -Werror -Wno-psabi -c -std=c++11 -MMD -MD -c +CFLAGS = -ggdb3 -DDEBUG -W -Werror -std=c++11 -MMD -MD else -CFLAGS = -c -W -Werror -Wno-psabi -std=c++11 -MMD -MD -c +CFLAGS = -W -Werror -std=c++11 -MMD -MD endif -LDFLAGS=-pthread - -URFSRCS = Buffer.cpp Callsign.cpp CallsignList.cpp CallsignListItem.cpp Client.cpp Clients.cpp DCSClient.cpp DCSProtocol.cpp DExtraClient.cpp DExtraPeer.cpp DExtraProtocol.cpp DPlusClient.cpp DPlusProtocol.cpp DVFramePacket.cpp DVHeaderPacket.cpp GateKeeper.cpp IP.cpp Notification.cpp Packet.cpp PacketStream.cpp PeerCallsignList.cpp Peer.cpp Peers.cpp Protocol.cpp Protocols.cpp Reflector.cpp SEProtocol.cpp UDPSocket.cpp User.cpp Users.cpp Version.cpp Main.cpp BMClient.cpp BMPeer.cpp BMProtocol.cpp BPTC19696.cpp CRC.cpp DMRIdDir.cpp DMRIdDirFile.cpp DMRIdDirHttp.cpp NXDNIdDir.cpp NXDNIdDirFile.cpp NXDNIdDirHttp.cpp DMRMMDVMClient.cpp DMRMMDVMProtocol.cpp DMRPlusClient.cpp DMRPlusProtocol.cpp Golay2087.cpp Golay24128.cpp Hamming.cpp M17Client.cpp M17CRC.cpp M17Packet.cpp M17Client.cpp M17Protocol.cpp NXDNClient.cpp NXDNProtocol.cpp P25Client.cpp P25Protocol.cpp QR1676.cpp RS129.cpp Semaphore.cpp USRPClient.cpp USRPProtocol.cpp Utils.cpp WiresXCmd.cpp WiresXCmdHandler.cpp WiresXInfo.cpp URFClient.cpp URFProtocol.cpp URFPeer.cpp YSFClient.cpp YSFConvolution.cpp YSFFich.cpp YSFNode.cpp YSFNodeDir.cpp YSFNodeDirFile.cpp YSFNodeDirHttp.cpp YSFPayload.cpp YSFProtocol.cpp YSFUtils.cpp - -G3SRCS = G3Client.cpp G3Protocol.cpp RawSocket.cpp UDPMsgSocket.cpp - -SRCS = $(URFSRCS) -ifeq ($(use_g3), true) -SRCS += $(G3SRCS) -endif - -ifeq ($(ysf_db), true) -LDFLAGS += `mysql_config --libs` -endif - -ifdef tc_ip -SRCS += CodecStream.cpp UnixDgramSocket.cpp -endif +LDFLAGS=-pthread -lcurl +SRCS = $(wildcard *.cpp) OBJS = $(SRCS:.cpp=.o) DEPS = $(SRCS:.cpp=.d) +DBUTILOBJS = Configure.o CurlGet.o Lookup.o LookupDmr.o LookupNxdn.o LookupYsf.o YSFNode.o Callsign.o -EXE=urfd - -all : $(EXE) +all : $(EXE) $(INICHECK) $(DBUTIL) $(EXE) : $(OBJS) - $(CC) $^ -o $@ $(LDFLAGS) + $(CXX) $^ -o $@ $(LDFLAGS) + +$(INICHECK) : Configure.cpp CurlGet.o + $(CXX) -DINICHECK $(CFLAGS) $< CurlGet.o -o $@ -lcurl + +$(DBUTIL) : Main.cpp $(DBUTILOBJS) + $(CXX) -DUTILITY $(CFLAGS) $< $(DBUTILOBJS) -o $@ -pthread -lcurl %.o : %.cpp - g++ $(CFLAGS) $< -o $@ + $(CXX) $(CFLAGS) -c $< -o $@ clean : - $(RM) *.o *.d urfd + $(RM) *.o *.d $(EXE) $(INICHECK) $(DBUTIL) -include $(DEPS) install : - ln -f -s $(shell pwd)/$(EXE).blacklist $(CFGDIR)/$(EXE).blacklist - ln -f -s $(shell pwd)/$(EXE).whitelist $(CFGDIR)/$(EXE).whitelist - ln -f -s $(shell pwd)/$(EXE).interlink $(CFGDIR)/$(EXE).interlink -ifeq ($(use_g3), true) - ln -f -s $(shell pwd)/$(EXE).terminal $(CFGDIR)/$(EXE).terminal -endif - cp -f ../systemd/$(EXE).service /etc/systemd/system/ + cp -f $(EXE).service /etc/systemd/system/ cp -f $(EXE) $(BINDIR) systemctl enable $(EXE).service systemctl daemon-reload systemctl start $(EXE) uninstall : - rm -f $(CFGDIR)/$(EXE).blacklist - rm -f $(CFGDIR)/$(EXE).whitelist - rm -f $(CFGDIR)/$(EXE).interlink - rm -f $(CFGDIR)/$(EXE).terminal systemctl stop $(EXE).service - rm -f $(CFGDIR)/dmrid.dat systemctl disable $(EXE).service - rm -f /etc/systemd/system/$(EXE).service + $(RM) /etc/systemd/system/$(EXE).service systemctl daemon-reload + $(RM) $(BINDIR)/$(EXE) diff --git a/reflector/NXDNClient.cpp b/reflector/NXDNClient.cpp index 64cb428..37b933e 100644 --- a/reflector/NXDNClient.cpp +++ b/reflector/NXDNClient.cpp @@ -17,7 +17,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include "NXDNClient.h" diff --git a/reflector/NXDNClient.h b/reflector/NXDNClient.h index e75c118..fb809cb 100644 --- a/reflector/NXDNClient.h +++ b/reflector/NXDNClient.h @@ -19,7 +19,9 @@ #pragma once +#include "Defines.h" #include "Client.h" + class CNXDNClient : public CClient { public: diff --git a/reflector/NXDNIdDir.cpp b/reflector/NXDNIdDir.cpp deleted file mode 100644 index 2d5d8d3..0000000 --- a/reflector/NXDNIdDir.cpp +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. - -// urfd -- The universal reflector -// Copyright © 2021 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 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include -#include "Main.h" -#include "Reflector.h" -#include "NXDNIdDir.h" -#include "NXDNIdDirFile.h" -#include "NXDNIdDirHttp.h" - -//////////////////////////////////////////////////////////////////////////////////////// -// constructor & destructor - -CNXDNidDir::CNXDNidDir() -{ - keep_running = true; -} - -CNXDNidDir::~CNXDNidDir() -{ - Close(); -} - - -//////////////////////////////////////////////////////////////////////////////////////// -// init & close - -bool CNXDNidDir::Init(void) -{ - // load content - Reload(); - - // reset run flag - keep_running = true; - - // start thread; - m_Future = std::async(std::launch::async, &CNXDNidDir::Thread, this); - - return true; -} - -void CNXDNidDir::Close(void) -{ - keep_running = false; - if ( m_Future.valid() ) - { - m_Future.get(); - } -} - -//////////////////////////////////////////////////////////////////////////////////////// -// thread - -void CNXDNidDir::Thread() -{ - while (keep_running) - { - // Wait DMRIDDB_REFRESH_RATE minutes - for (int i=0; i<30*NXDNIDDB_REFRESH_RATE && keep_running; i++) - std::this_thread::sleep_for(std::chrono::milliseconds(2000)); - - // have lists files changed ? - if ( NeedReload() ) - { - Reload(); - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////// -// Reload - -bool CNXDNidDir::Reload(void) -{ - CBuffer buffer; - bool ok = false; - - if ( LoadContent(&buffer) ) - { - Lock(); - { - ok = RefreshContent(buffer); - } - Unlock(); - } - return ok; -} - - -//////////////////////////////////////////////////////////////////////////////////////// -// find - -const CCallsign *CNXDNidDir::FindCallsign(uint16_t nxdnid) -{ - auto found = m_CallsignMap.find(nxdnid); - if ( found != m_CallsignMap.end() ) - { - return &(found->second); - } - return nullptr; -} - -uint16_t CNXDNidDir::FindNXDNid(const CCallsign &callsign) -{ - auto found = m_NXDNidMap.find(callsign); - if ( found != m_NXDNidMap.end() ) - { - return (found->second); - } - return 0; -} - - -//////////////////////////////////////////////////////////////////////////////////////// -// syntax helpers - -bool CNXDNidDir::IsValidNXDNid(const char *sz) -{ - bool ok = false; - size_t n = ::strlen(sz); - if ( (n > 0) && (n <= 5) ) - { - ok = true; - for ( size_t i = 0; (i < n) && ok; i++ ) - { - ok &= ::isdigit(sz[i]); - } - } - return ok; -} diff --git a/reflector/NXDNIdDir.h b/reflector/NXDNIdDir.h deleted file mode 100644 index 4d9a8af..0000000 --- a/reflector/NXDNIdDir.h +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. - -// urfd -- The universal reflector -// Copyright © 2021 Thomas A. Early N7TAE -// Copyright © 2021 Doug McLain AD8DP -// -// 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 3 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 . - -#pragma once - -#include -#include -#include -#include -#include "Buffer.h" -#include "Callsign.h" - -// compare function for std::map::find - -struct CNXDNidDirCallsignCompare -{ - bool operator() (const CCallsign &cs1, const CCallsign &cs2) const - { return cs1.HasLowerCallsign(cs2);} -}; - - -//////////////////////////////////////////////////////////////////////////////////////// - -class CNXDNidDir -{ -public: - // constructor - CNXDNidDir(); - - // destructor - ~CNXDNidDir(); - - // init & close - virtual bool Init(void); - virtual void Close(void); - - // locks - void Lock(void) { m_Mutex.lock(); } - void Unlock(void) { m_Mutex.unlock(); } - - // refresh - virtual bool LoadContent(CBuffer *) { return false; } - virtual bool RefreshContent(const CBuffer &) { return false; } - - // find - const CCallsign *FindCallsign(uint16_t); - uint16_t FindNXDNid(const CCallsign &); - -protected: - // thread - void Thread(); - - // reload helpers - bool Reload(void); - virtual bool NeedReload(void) { return false; } - bool IsValidNXDNid(const char *); - -protected: - // data - std::map m_CallsignMap; - std::map m_NXDNidMap; - - // Lock() - std::mutex m_Mutex; - - // thread - std::atomic keep_running; - std::future m_Future; - -}; diff --git a/reflector/NXDNIdDirFile.cpp b/reflector/NXDNIdDirFile.cpp deleted file mode 100644 index 73b4d95..0000000 --- a/reflector/NXDNIdDirFile.cpp +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. - -// urfd -- The universal reflector -// Copyright © 2021 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 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include -#include -#include -#include "Main.h" -#include "NXDNIdDirFile.h" - - -#if (NXDNIDDB_USE_RLX_SERVER == 0) -CNXDNidDirFile g_NXDNidDir; -#endif - -//////////////////////////////////////////////////////////////////////////////////////// -// constructor & destructor - -CNXDNidDirFile::CNXDNidDirFile() -{ - memset(&m_LastModTime, 0, sizeof(time_t)); -} - - -//////////////////////////////////////////////////////////////////////////////////////// -// init & close - -bool CNXDNidDirFile::Init(void) -{ - return CNXDNidDir::Init(); -} - -//////////////////////////////////////////////////////////////////////////////////////// -// refresh - -bool CNXDNidDirFile::NeedReload(void) -{ - bool needReload = false; - - time_t time; - if ( GetLastModTime(&time) ) - { - needReload = time != m_LastModTime; - } - return needReload; -} - -bool CNXDNidDirFile::LoadContent(CBuffer *buffer) -{ - bool ok = false; - std::ifstream file; - std::streampos size; - - // open file - file.open(NXDNIDDB_PATH, std::ios::in | std::ios::binary | std::ios::ate); - if ( file.is_open() ) - { - // read file - size = file.tellg(); - if ( size > 0 ) - { - // read file into buffer - buffer->resize((int)size+1); - file.seekg (0, std::ios::beg); - file.read((char *)buffer->data(), (int)size); - - // close file - file.close(); - - // update time - GetLastModTime(&m_LastModTime); - - // done - ok = true; - } - } - - // done - return ok; -} - -bool CNXDNidDirFile::RefreshContent(const CBuffer &buffer) -{ - bool ok = false; - - // clear directory - m_CallsignMap.clear(); - m_NXDNidMap.clear(); - - // scan buffer - if ( buffer.size() > 0 ) - { - // crack it - char *ptr1 = (char *)buffer.data(); - char *ptr2; - - // get next line - while ( (ptr2 = ::strchr(ptr1, '\n')) != nullptr ) - { - *ptr2 = 0; - // get items - char *nxdnid; - char *callsign; - if ( ((nxdnid = ::strtok(ptr1, ",")) != nullptr) && IsValidNXDNid(nxdnid) ) - { - if ( ((callsign = ::strtok(nullptr, ",")) != nullptr) ) - { - // new entry - uint16_t us = atoi(nxdnid); - CCallsign cs(callsign, 0, us); - if ( cs.IsValid() ) - { - m_CallsignMap.insert(std::pair(us, cs)); - m_NXDNidMap.insert(std::pair(cs,us)); - } - } - } - // next line - ptr1 = ptr2+1; - } - - // done - ok = true; - } - - // report - std::cout << "Read " << m_NXDNidMap.size() << " NXDN ids from file " << NXDNIDDB_PATH << std::endl; - - // done - return ok; -} - - -bool CNXDNidDirFile::GetLastModTime(time_t *time) -{ - bool ok = false; - - struct stat fileStat; - if( ::stat(NXDNIDDB_PATH, &fileStat) != -1 ) - { - *time = fileStat.st_mtime; - ok = true; - } - return ok; -} diff --git a/reflector/NXDNIdDirHttp.cpp b/reflector/NXDNIdDirHttp.cpp deleted file mode 100644 index 94bbc22..0000000 --- a/reflector/NXDNIdDirHttp.cpp +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. - -// urfd -- The universal reflector -// Copyright © 2021 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 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include -#include "Main.h" -#include "Reflector.h" -#include "NXDNIdDirHttp.h" - -#if (NXDNIDDB_USE_RLX_SERVER == 1) -CNXDNidDirHttp g_NXDNidDir; -#endif - - - - -//////////////////////////////////////////////////////////////////////////////////////// -// refresh - -bool CNXDNidDirHttp::LoadContent(CBuffer *buffer) -{ - // get file from xlxapi server - return HttpGet("www.dudetronics.com", "ar-dns/NXDN.csv", 80, buffer); -} - -bool CNXDNidDirHttp::RefreshContent(const CBuffer &buffer) -{ - bool ok = false; - - // clear directory - m_CallsignMap.clear(); - m_NXDNidMap.clear(); - - // scan file - if ( buffer.size() > 0 ) - { - char *ptr1 = (char *)buffer.data(); - char *ptr2; - // get next line - while ( (ptr2 = ::strchr(ptr1, '\n')) != nullptr ) - { - std::cout << "newline: " << std::string(ptr2) << std::endl; - *ptr2 = 0; - // get items - char *nxdnid; - char *callsign; - if ( ((nxdnid = ::strtok(ptr1, ",")) != nullptr) && IsValidNXDNid(nxdnid) ) - { - if ( ((callsign = ::strtok(nullptr, ",")) != nullptr) ) - { - // new entry - uint16_t us = atoi(nxdnid); - CCallsign cs(callsign, 0, us); - if ( cs.IsValid() ) - { - m_CallsignMap.insert(std::pair(us, cs)); - m_NXDNidMap.insert(std::pair(cs,us)); - } - } - } - // next line - ptr1 = ptr2+1; - } - // done - ok = true; - } - - // report - std::cout << "Read " << m_NXDNidMap.size() << " NXDN ids from xlxapi.rlx.lu database " << std::endl; - - // done - return ok; -} - - -//////////////////////////////////////////////////////////////////////////////////////// -// httpd helpers - -#define NXDNID_HTTPGET_SIZEMAX (256) - -bool CNXDNidDirHttp::HttpGet(const char *hostname, const char *filename, int port, CBuffer *buffer) -{ - bool ok = false; - int sock_id; - - // open socket - if ( (sock_id = ::socket(AF_INET, SOCK_STREAM, 0)) >= 0 ) - { - // get hostname address - struct sockaddr_in servaddr; - struct hostent *hp; - memset(&servaddr,0,sizeof(servaddr)); - if( (hp = gethostbyname(hostname)) != nullptr ) - { - // dns resolved - memcpy((char *)&servaddr.sin_addr.s_addr, (char *)hp->h_addr, hp->h_length); - servaddr.sin_port = htons(port); - servaddr.sin_family = AF_INET; - - // connect - if ( ::connect(sock_id, (struct sockaddr *)&servaddr, sizeof(servaddr)) == 0) - { - // send the GET request - char request[NXDNID_HTTPGET_SIZEMAX]; - ::sprintf(request, "GET /%s HTTP/1.0\r\nFrom: %s\r\nUser-Agent: urfd\r\n\r\n", - filename, (const char *)g_Reflector.GetCallsign()); - ::write(sock_id, request, strlen(request)); - - // config receive timeouts - fd_set read_set; - struct timeval timeout; - timeout.tv_sec = 5; - timeout.tv_usec = 0; - FD_ZERO(&read_set); - FD_SET(sock_id, &read_set); - - // get the reply back - buffer->clear(); - bool done = false; - do - { - char buf[1440]; - ssize_t len = 0; - select(sock_id+1, &read_set, nullptr, nullptr, &timeout); - //if ( (ret > 0) || ((ret < 0) && (errno == EINPROGRESS)) ) - //if ( ret >= 0 ) - //{ - usleep(5000); - len = read(sock_id, buf, 1440); - if ( len > 0 ) - { - buffer->Append((uint8_t *)buf, (int)len); - ok = true; - } - //} - done = (len <= 0); - - } - while (!done); - buffer->Append((uint8_t)0); - - // and disconnect - close(sock_id); - } - else - { - std::cout << "Cannot establish connection with host " << hostname << std::endl; - } - } - else - { - std::cout << "Host " << hostname << " not found" << std::endl; - } - - } - else - { - std::cout << "Failed to open wget socket" << std::endl; - } - - // done - return ok; -} diff --git a/reflector/NXDNProtocol.cpp b/reflector/NXDNProtocol.cpp index e26135c..fb2381d 100644 --- a/reflector/NXDNProtocol.cpp +++ b/reflector/NXDNProtocol.cpp @@ -17,14 +17,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include #include "NXDNClient.h" #include "NXDNProtocol.h" #include "YSFDefines.h" -#include "Reflector.h" -#include "GateKeeper.h" #include "Golay24128.h" +#include "Global.h" const uint8_t NXDN_LICH_RFCT_RDCH = 2U; const uint8_t NXDN_LICH_USC_SACCH_NS = 0U; @@ -53,6 +52,10 @@ CNXDNProtocol::CNXDNProtocol() bool CNXDNProtocol::Initialize(const char *type, const EProtocol ptype, const uint16_t port, const bool has_ipv4, const bool has_ipv6) { + // config value + m_ReflectorId = g_Configure.GetUnsigned(g_Keys.nxdn.reflectorid); + m_AutolinkModule = g_Configure.GetAutolinkModule(g_Keys.nxdn.autolinkmod); + // base class if (! CProtocol::Initialize(type, ptype, port, has_ipv4, has_ipv6)) return false; @@ -130,9 +133,8 @@ void CNXDNProtocol::Task(void) auto newclient = std::make_shared(Callsign, Ip); // aautolink, if enabled -#if NXDN_AUTOLINK_ENABLE - newclient->SetReflectorModule(NXDN_AUTOLINK_MODULE); -#endif + if (m_AutolinkModule) + newclient->SetReflectorModule(m_AutolinkModule); // and append clients->AddClient(newclient); @@ -141,7 +143,7 @@ void CNXDNProtocol::Task(void) { client->Alive(); } - + // acknowledge the request -- NXDNReflector simply echoes the packet Send(Buffer, Ip); // and done @@ -244,11 +246,10 @@ void CNXDNProtocol::OnDvHeaderPacketIn(std::unique_ptr &Header, void CNXDNProtocol::HandleQueue(void) { - m_Queue.Lock(); - while ( !m_Queue.empty() ) + while (! m_Queue.IsEmpty()) { // get the packet - auto packet = m_Queue.pop(); + auto packet = m_Queue.Pop(); // get our sender's id const auto mod = packet->GetPacketModule(); @@ -308,7 +309,6 @@ void CNXDNProtocol::HandleQueue(void) g_Reflector.ReleaseClients(); } } - m_Queue.Unlock(); } //////////////////////////////////////////////////////////////////////////////////////// @@ -408,24 +408,24 @@ bool CNXDNProtocol::IsValidDvFramePacket(const CIp &Ip, const CBuffer &Buffer, s rpt1.SetCSModule(NXDN_MODULE_ID); rpt2.SetCSModule(' '); header = std::unique_ptr(new CDvHeaderPacket(csMY, CCallsign("CQCQCQ"), rpt1, rpt2, m_uiStreamId, false)); - + if ( g_GateKeeper.MayTransmit(header->GetMyCallsign(), Ip, EProtocol::nxdn, header->GetRpt2Module()) ) { - OnDvHeaderPacketIn(header, Ip); + OnDvHeaderPacketIn(header, Ip); } } // get DV frames uint8_t ambe49[7]; - + uint8_t ambe0[9]; uint8_t ambe1[9]; uint8_t ambe2[9]; uint8_t ambe3[9]; - + memcpy(ambe49, Buffer.data() + 15, 7); encode(ambe49, ambe0); - + uint8_t t[7]; const uint8_t *d = &(Buffer.data()[21]); for(int i = 0; i < 6; ++i){ @@ -436,10 +436,10 @@ bool CNXDNProtocol::IsValidDvFramePacket(const CIp &Ip, const CBuffer &Buffer, s memcpy(ambe49, t, 7); encode(ambe49, ambe1); - + memcpy(ambe49, Buffer.data() + 29, 7); encode(ambe49, ambe2); - + d = &(Buffer.data()[35]); for(int i = 0; i < 6; ++i){ t[i] = d[i] << 1; @@ -479,15 +479,15 @@ bool CNXDNProtocol::EncodeNXDNHeaderPacket(const CDvHeaderPacket &Header, CBuffe { Buffer.resize(43); uint16_t NXDNId = Header.GetMyCallsign().GetNXDNid(); - uint16_t RptrId = NXDN_REFID; - + uint16_t RptrId = m_ReflectorId; + memcpy(Buffer.data(), "NXDND", 5); Buffer.data()[5U] = (NXDNId >> 8) & 0xFFU; Buffer.data()[6U] = (NXDNId >> 0) & 0xFFU; Buffer.data()[7U] = (RptrId >> 8) & 0xFFU; Buffer.data()[8U] = (RptrId >> 0) & 0xFFU; Buffer.data()[9U] = 0x01U; - + const uint8_t idle[3U] = {0x10, 0x00, 0x00}; m_lich = 0; memset(m_sacch, 0, 5U); @@ -514,7 +514,7 @@ bool CNXDNProtocol::EncodeNXDNHeaderPacket(const CDvHeaderPacket &Header, CBuffe set_layer3_blks(0U); memcpy(&Buffer.data()[15U], m_layer3, 14U); memcpy(&Buffer.data()[29U], m_layer3, 14U); - + if (Buffer.data()[10U] == 0x81U || Buffer.data()[10U] == 0x83U) { Buffer.data()[9U] |= Buffer.data()[15U] == 0x01U ? 0x04U : 0x00U; Buffer.data()[9U] |= Buffer.data()[15U] == 0x08U ? 0x08U : 0x00U; @@ -526,7 +526,7 @@ bool CNXDNProtocol::EncodeNXDNHeaderPacket(const CDvHeaderPacket &Header, CBuffe Buffer.data()[9U] |= Buffer.data()[12U] == 0x08U ? 0x08U : 0x00U; } } - + return true; } @@ -535,15 +535,15 @@ bool CNXDNProtocol::EncodeNXDNPacket(const CDvHeaderPacket &Header, uint32_t seq uint8_t ambe[28]; Buffer.resize(43); uint16_t NXDNId = Header.GetMyCallsign().GetNXDNid(); - uint16_t RptrId = NXDN_REFID; - + uint16_t RptrId = m_ReflectorId; + memcpy(Buffer.data(), "NXDND", 5); Buffer.data()[5U] = (NXDNId >> 8) & 0xFFU; Buffer.data()[6U] = (NXDNId >> 0) & 0xFFU; Buffer.data()[7U] = (RptrId >> 8) & 0xFFU; Buffer.data()[8U] = (RptrId >> 0) & 0xFFU; Buffer.data()[9U] = 0x01U; - + uint8_t msg[3U]; m_lich = 0; memset(m_sacch, 0, 5U); @@ -590,7 +590,7 @@ bool CNXDNProtocol::EncodeNXDNPacket(const CDvHeaderPacket &Header, uint32_t seq for(int i = 0; i < 4; ++i){ decode(DvFrames[i].GetCodecData(ECodecType::dmr), ambe+(i*7)); } - + memcpy(&Buffer.data()[15], ambe, 7); for(int i = 0; i < 7; ++i){ Buffer.data()[21+i] |= (ambe[7+i] >> 1); @@ -604,7 +604,7 @@ bool CNXDNProtocol::EncodeNXDNPacket(const CDvHeaderPacket &Header, uint32_t seq Buffer.data()[36+i] = (ambe[21+i] & 1) << 7; } Buffer.data()[41] |= (ambe[27] >> 2); - + return true; } @@ -657,11 +657,11 @@ void CNXDNProtocol::decode(const unsigned char* in, unsigned char* out) const void CNXDNProtocol::encode(const unsigned char* in, unsigned char* out) const { - + unsigned int aOrig = 0U; unsigned int bOrig = 0U; unsigned int cOrig = 0U; - + unsigned int MASK = 0x000800U; for (unsigned int i = 0U; i < 12U; i++, MASK >>= 1) { unsigned int n1 = i + 0U; @@ -838,4 +838,3 @@ void CNXDNProtocol::encode_crc6(uint8_t *d, uint8_t len) WRITE_BIT(d, n, b); } } - diff --git a/reflector/NXDNProtocol.h b/reflector/NXDNProtocol.h index 66c6896..ceb5dd6 100644 --- a/reflector/NXDNProtocol.h +++ b/reflector/NXDNProtocol.h @@ -19,6 +19,7 @@ #pragma once +#include "Defines.h" #include "Timer.h" #include "Protocol.h" #include "DVHeaderPacket.h" @@ -80,10 +81,10 @@ protected: // uiStreamId helpers uint32_t IpToStreamId(const CIp &) const; - + void encode(const unsigned char*, unsigned char*) const; void decode(const unsigned char*, unsigned char*) const; - + uint8_t get_lich_fct(uint8_t); void set_lich_rfct(uint8_t); void set_lich_fct(uint8_t); @@ -121,4 +122,7 @@ protected: uint8_t m_lich; uint8_t m_sacch[5]; uint8_t m_layer3[22]; + + uint16_t m_ReflectorId; + char m_AutolinkModule; }; diff --git a/reflector/Notification.cpp b/reflector/Notification.cpp index 71208e3..6808a02 100644 --- a/reflector/Notification.cpp +++ b/reflector/Notification.cpp @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include "Notification.h" CNotification::CNotification() diff --git a/reflector/NotificationQueue.h b/reflector/NotificationQueue.h index 57839ad..70b894a 100644 --- a/reflector/NotificationQueue.h +++ b/reflector/NotificationQueue.h @@ -32,12 +32,6 @@ class CNotificationQueue { public: - // constructor - CNotificationQueue() {} - - // destructor - ~CNotificationQueue() {} - // lock void Lock() { m_Mutex.lock(); } void Unlock() { m_Mutex.unlock(); } diff --git a/reflector/P25Client.cpp b/reflector/P25Client.cpp index 18a0bcb..91c8041 100644 --- a/reflector/P25Client.cpp +++ b/reflector/P25Client.cpp @@ -17,7 +17,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include "P25Client.h" diff --git a/reflector/P25Client.h b/reflector/P25Client.h index 7532a92..aafce95 100644 --- a/reflector/P25Client.h +++ b/reflector/P25Client.h @@ -18,8 +18,9 @@ // along with this program. If not, see . #pragma once - +#include "Defines.h" #include "Client.h" + class CP25Client : public CClient { public: diff --git a/reflector/P25Protocol.cpp b/reflector/P25Protocol.cpp index 9cbe14a..c83e33c 100644 --- a/reflector/P25Protocol.cpp +++ b/reflector/P25Protocol.cpp @@ -17,13 +17,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include #include "P25Client.h" #include "P25Protocol.h" -#include "Reflector.h" -#include "GateKeeper.h" +#include "Global.h" const uint8_t REC62[] = {0x62U, 0x02U, 0x02U, 0x0CU, 0x0BU, 0x12U, 0x64U, 0x00U, 0x00U, 0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,0x00U, 0x00U, 0x00U, 0x00U, 0x00U}; const uint8_t REC63[] = {0x63U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U}; @@ -50,6 +49,11 @@ const uint8_t REC80[] = {0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, bool CP25Protocol::Initialize(const char *type, const EProtocol ptype, const uint16_t port, const bool has_ipv4, const bool has_ipv6) { + // config data + m_ReflectorId = g_Configure.GetUnsigned(g_Keys.p25.reflectorid); + m_DefaultId = g_Configure.GetUnsigned(g_Keys.mmdvm.defaultid); + m_AutolinkModule = g_Configure.GetAutolinkModule(g_Keys.p25.autolinkmod); + m_uiStreamId = 0; // base class if (! CProtocol::Initialize(type, ptype, port, has_ipv4, has_ipv6)) @@ -118,9 +122,8 @@ void CP25Protocol::Task(void) auto newclient = std::make_shared(Callsign, Ip); // aautolink, if enabled -#if P25_AUTOLINK_ENABLE - newclient->SetReflectorModule(P25_AUTOLINK_MODULE); -#endif + if (m_AutolinkModule) + newclient->SetReflectorModule(m_AutolinkModule); // and append clients->AddClient(newclient); @@ -129,7 +132,7 @@ void CP25Protocol::Task(void) { client->Alive(); } - + // acknowledge the request -- P25Reflector simply echoes the packet Send(Buffer, Ip); // and done @@ -226,11 +229,10 @@ void CP25Protocol::OnDvHeaderPacketIn(std::unique_ptr &Header, void CP25Protocol::HandleQueue(void) { - m_Queue.Lock(); - while ( !m_Queue.empty() ) + while (! m_Queue.IsEmpty()) { // get the packet - auto packet = m_Queue.pop(); + auto packet = m_Queue.Pop(); // get our sender's id const auto module = packet->GetPacketModule(); @@ -273,7 +275,6 @@ void CP25Protocol::HandleQueue(void) } } } - m_Queue.Unlock(); } @@ -308,7 +309,7 @@ bool CP25Protocol::IsValidDvPacket(const CIp &Ip, const CBuffer &Buffer, std::un { int offset = 0; bool last = false; - + switch (Buffer.data()[0U]) { case 0x62U: offset = 10U; @@ -357,7 +358,7 @@ bool CP25Protocol::IsValidDvPacket(const CIp &Ip, const CBuffer &Buffer, std::un default: break; } - + frame = std::unique_ptr(new CDvFramePacket(&(Buffer.data()[offset]), m_uiStreamId, last)); return true; } @@ -387,19 +388,18 @@ bool CP25Protocol::IsValidDvHeaderPacket(const CIp &Ip, const CBuffer &Buffer, s void CP25Protocol::EncodeP25Packet(const CDvHeaderPacket &Header, const CDvFramePacket &DvFrame, uint32_t iSeq, CBuffer &Buffer, bool islast) const { uint32_t uiSrcId = Header.GetMyCallsign().GetDmrid(); - uint32_t uiRptrId = P25_REFID; - + if(uiSrcId == 0){ - uiSrcId = DMRMMDVM_DEFAULTID; + uiSrcId = m_DefaultId; } - + if(islast) { Buffer.resize(17); ::memcpy(Buffer.data(), REC80, 17U); return; } - + switch (iSeq % 18) { case 0x00U: Buffer.resize(22); @@ -420,9 +420,9 @@ void CP25Protocol::EncodeP25Packet(const CDvHeaderPacket &Header, const CDvFrame Buffer.resize(17); ::memcpy(Buffer.data(), REC65, 17U); ::memcpy(Buffer.data() + 5U, DvFrame.GetCodecData(ECodecType::p25), 11U); - Buffer.data()[1U] = (uiRptrId >> 16) & 0xFFU; - Buffer.data()[2U] = (uiRptrId >> 8) & 0xFFU; - Buffer.data()[3U] = (uiRptrId >> 0) & 0xFFU; + Buffer.data()[1U] = (m_ReflectorId >> 16) & 0xFFU; + Buffer.data()[2U] = (m_ReflectorId >> 8) & 0xFFU; + Buffer.data()[3U] = (m_ReflectorId >> 0) & 0xFFU; break; case 0x04U: Buffer.resize(17); @@ -528,4 +528,3 @@ void CP25Protocol::HandleKeepalives(void) } g_Reflector.ReleaseClients(); } - diff --git a/reflector/P25Protocol.h b/reflector/P25Protocol.h index 38f7a36..7829fe9 100644 --- a/reflector/P25Protocol.h +++ b/reflector/P25Protocol.h @@ -19,6 +19,7 @@ #pragma once +#include "Defines.h" #include "Timer.h" #include "Protocol.h" #include "DVHeaderPacket.h" @@ -53,7 +54,7 @@ protected: // queue helper void HandleQueue(void); void HandleKeepalives(void); - + // stream helpers void OnDvHeaderPacketIn(std::unique_ptr &, const CIp &); @@ -72,4 +73,9 @@ protected: // for queue header caches std::unordered_map m_StreamsCache; uint32_t m_uiStreamId; + + // config data + uint32_t m_ReflectorId; + uint32_t m_DefaultId; + char m_AutolinkModule; }; diff --git a/reflector/Packet.cpp b/reflector/Packet.cpp index 9ca9b10..ed4d3a1 100644 --- a/reflector/Packet.cpp +++ b/reflector/Packet.cpp @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" +#include #include "Packet.h" // default constructor diff --git a/reflector/Packet.h b/reflector/Packet.h index c09e419..0ee7012 100644 --- a/reflector/Packet.h +++ b/reflector/Packet.h @@ -40,13 +40,8 @@ public: CPacket(uint16_t sid, uint8_t dstarpid, uint8_t dmrpid, uint8_t dmrsubpid, uint8_t ysfpid, uint8_t ysfsubpid, uint8_t ysfsubpidmax, ECodecType, bool lastpacket); CPacket(const CM17Packet &); - // destructor - virtual ~CPacket() {} - - // virtual duplication - virtual std::unique_ptr Duplicate(void) const = 0; - // identity + virtual std::unique_ptr Copy(void) = 0; virtual bool IsDvHeader(void) const = 0; virtual bool IsDvFrame(void) const = 0; bool IsLastPacket(void) const { return m_bLastPacket; } diff --git a/reflector/PacketQueue.h b/reflector/PacketQueue.h deleted file mode 100644 index 8851cd5..0000000 --- a/reflector/PacketQueue.h +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. - -// urfd -- The universal reflector -// Copyright © 2021 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 3 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 . - -#pragma once - -#include "Packet.h" - -class CClient; - -class CPacketQueue -{ -public: - // destructor - virtual ~CPacketQueue() {} - - // lock - void Lock() - { - m_Mutex.lock(); - } - - void Unlock() - { - m_Mutex.unlock(); - } - - // pass thru - std::unique_ptr pop() - { - auto pack = std::move(queue.front()); - queue.pop(); - return std::move(pack); - } - - bool empty() const - { - return queue.empty(); - } - - void push(std::unique_ptr &packet) - { - queue.push(std::move(packet)); - } - -protected: - // status - bool m_bOpen; - uint16_t m_uiStreamId; - std::mutex m_Mutex; - - // the queue - std::queue> queue; -}; diff --git a/reflector/PacketStream.cpp b/reflector/PacketStream.cpp index bcd2ef5..7582c28 100644 --- a/reflector/PacketStream.cpp +++ b/reflector/PacketStream.cpp @@ -16,26 +16,31 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include "PacketStream.h" //////////////////////////////////////////////////////////////////////////////////////// // constructor -#ifdef TRANSCODED_MODULES -CPacketStream::CPacketStream(std::shared_ptr reader) -#else -CPacketStream::CPacketStream() -#endif +CPacketStream::CPacketStream(char module) : m_PSModule(module) { m_bOpen = false; m_uiStreamId = 0; m_uiPacketCntr = 0; m_OwnerClient = nullptr; -#ifdef TRANSCODED_MODULES m_CodecStream = nullptr; - m_TCReader = reader; -#endif +} + +bool CPacketStream::InitCodecStream() +{ + m_CodecStream = std::unique_ptr(new CCodecStream(this, m_PSModule)); + if (m_CodecStream) + return m_CodecStream->InitCodecStream(); + else + { + std::cerr << "Could not create a CCodecStream for module '" << m_PSModule << "'" << std::endl; + return true; + } } //////////////////////////////////////////////////////////////////////////////////////// @@ -53,19 +58,10 @@ bool CPacketStream::OpenPacketStream(const CDvHeaderPacket &DvHeader, std::share m_DvHeader = DvHeader; m_OwnerClient = client; m_LastPacketTime.start(); -#ifdef TRANSCODED_MODULES - if (DvHeader.IsLocalOrigin()) // we only need transcoding if the source is local - { - auto mod = DvHeader.GetRpt2Module(); - if (std::string::npos != std::string(TRANSCODED_MODULES).find(mod)) - { - m_CodecStream = std::unique_ptr(new CCodecStream(this, m_uiStreamId, DvHeader.GetCodecIn(), m_TCReader)); - } - } -#endif + if (m_CodecStream) + m_CodecStream->ResetStats(m_uiStreamId, m_DvHeader.GetCodecIn()); return true; } - return false; } @@ -75,9 +71,8 @@ void CPacketStream::ClosePacketStream(void) m_bOpen = false; m_uiStreamId = 0; m_OwnerClient.reset(); -#ifdef TRANSCODED_MODULES - m_CodecStream.reset(); -#endif + if (m_CodecStream) + m_CodecStream->ReportStats(); } //////////////////////////////////////////////////////////////////////////////////////// @@ -91,34 +86,20 @@ void CPacketStream::Push(std::unique_ptr Packet) { Packet->UpdatePids(m_uiPacketCntr++); } - // transcoder avaliable ? -#ifdef TRANSCODED_MODULES - if ( m_CodecStream ) + // ... Is there a CodecStream (is this module transcoded)? + // AND Is this voice data? + // AND Is this from a local client and not from an interlinked URF + if ( m_CodecStream && Packet->IsDvFrame() && Packet->IsLocalOrigin()) { - // todo: verify no possibilty of double lock here - m_CodecStream->Lock(); - { - // transcoder ready & frame need transcoding ? - if (Packet->IsDvFrame()) - { - // yes, push packet to trancoder queue - // trancoder will push it after transcoding - // is completed - m_CodecStream->push(Packet); - } - else - { - // no, just bypass transcoder - push(Packet); - } - } - m_CodecStream->Unlock(); + // yes, push packet to trancoder queue + // trancoder will push it after transcoding + // is completed + m_CodecStream->Push(std::move(Packet)); } else -#endif { - // otherwise, push direct push - push(Packet); + // no, just bypass transcoder + m_Queue.Push(std::move(Packet)); } } diff --git a/reflector/PacketStream.h b/reflector/PacketStream.h index d35fad4..e7b7581 100644 --- a/reflector/PacketStream.h +++ b/reflector/PacketStream.h @@ -18,14 +18,10 @@ #pragma once -#include "PacketQueue.h" #include "Timer.h" #include "DVHeaderPacket.h" #include "Client.h" -#ifdef TRANSCODED_MODULES -#include "UnixDgramSocket.h" #include "CodecStream.h" -#endif //////////////////////////////////////////////////////////////////////////////////////// @@ -35,21 +31,18 @@ //////////////////////////////////////////////////////////////////////////////////////// // class -class CPacketStream : public CPacketQueue +class CPacketStream { public: - // constructor -#ifdef TRANSCODED_MODULES - CPacketStream(std::shared_ptr); -#else - CPacketStream(); -#endif + CPacketStream(char module); + bool InitCodecStream(); // open / close bool OpenPacketStream(const CDvHeaderPacket &, std::shared_ptr); void ClosePacketStream(void); // push & pop + void ReturnPacket(std::unique_ptr p) { m_Queue.Push(std::move(p)); } void Push(std::unique_ptr packet); void Tickle(void) { m_LastPacketTime.start(); } @@ -62,16 +55,20 @@ public: const CCallsign &GetUserCallsign(void) const { return m_DvHeader.GetMyCallsign(); } char GetRpt2Module(void) const { return m_DvHeader.GetRpt2Module(); } + // pass-through + std::unique_ptr Pop() { return m_Queue.Pop(); } + std::unique_ptr PopWait() { return m_Queue.PopWait(); } + bool IsEmpty() { return m_Queue.IsEmpty(); } + protected: // data + CSafePacketQueue> m_Queue; + const char m_PSModule; bool m_bOpen; uint16_t m_uiStreamId; uint32_t m_uiPacketCntr; CTimer m_LastPacketTime; CDvHeaderPacket m_DvHeader; std::shared_ptr m_OwnerClient; -#ifdef TRANSCODED_MODULES - std::shared_ptr m_TCReader; std::unique_ptr m_CodecStream; -#endif }; diff --git a/reflector/Peer.cpp b/reflector/Peer.cpp index e4b7c86..3a1638e 100644 --- a/reflector/Peer.cpp +++ b/reflector/Peer.cpp @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" +#include #include #include "Reflector.h" #include "Peer.h" @@ -125,20 +125,16 @@ void CPeer::WriteXml(std::ofstream &xmlFile) xmlFile << "" << std::endl; } -void CPeer::GetJsonObject(char *Buffer) +void CPeer::JsonReport(nlohmann::json &report) { - char sz[512]; - char mbstr[100]; - char cs[16]; - - if (std::strftime(mbstr, sizeof(mbstr), "%A %c", std::localtime(&m_LastHeardTime))) + nlohmann::json jpeer; + jpeer["Callsign"] = m_Callsign.GetCS(); + jpeer["Modules"] = m_ReflectorModules; + jpeer["Protocol"] = GetProtocolName(); + char s[100]; + if (std::strftime(s, sizeof(s), "%FT%TZ", std::gmtime(&m_ConnectTime))) { - m_Callsign.GetCallsignString(cs); - - ::sprintf(sz, "{\"callsign\":\"%s\",\"linkedto\":\"%s\",\"time\":\"%s\"}", - cs, - m_ReflectorModules, - mbstr); - ::strcat(Buffer, sz); + jpeer["ConnectTime"] = s; } + report["Peers"].push_back(jpeer); } diff --git a/reflector/Peer.h b/reflector/Peer.h index d39a146..bc1ddf9 100644 --- a/reflector/Peer.h +++ b/reflector/Peer.h @@ -68,7 +68,7 @@ public: // reporting virtual void WriteXml(std::ofstream &); - virtual void GetJsonObject(char *); + void JsonReport(nlohmann::json &report); protected: // data diff --git a/reflector/PeerCallsignList.cpp b/reflector/PeerCallsignList.cpp index 7b70951..5729d0a 100644 --- a/reflector/PeerCallsignList.cpp +++ b/reflector/PeerCallsignList.cpp @@ -16,11 +16,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#include #include -#include "Main.h" + #include "PeerCallsignList.h" -bool CPeerCallsignList::LoadFromFile(const char *filename) +bool CPeerCallsignList::LoadFromFile(const std::string &filename) { bool ok = false; char sz[256]; diff --git a/reflector/PeerCallsignList.h b/reflector/PeerCallsignList.h index 2ec8da6..b4cd2b9 100644 --- a/reflector/PeerCallsignList.h +++ b/reflector/PeerCallsignList.h @@ -18,7 +18,7 @@ #pragma once -#include "Main.h" + #include "CallsignList.h" //////////////////////////////////////////////////////////////////////////////////////// @@ -34,5 +34,5 @@ public: virtual ~CPeerCallsignList() {} // file io - bool LoadFromFile(const char *); + bool LoadFromFile(const std::string &filename); }; diff --git a/reflector/Peers.cpp b/reflector/Peers.cpp index 475602e..9be9c04 100644 --- a/reflector/Peers.cpp +++ b/reflector/Peers.cpp @@ -16,11 +16,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" -#include "Reflector.h" +#include "Global.h" #include "Peers.h" - //////////////////////////////////////////////////////////////////////////////////////// // constructor diff --git a/reflector/Protocol.cpp b/reflector/Protocol.cpp index 99b75fa..fb9db9e 100644 --- a/reflector/Protocol.cpp +++ b/reflector/Protocol.cpp @@ -16,11 +16,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" +#include "Defines.h" +#include "Global.h" #include "Protocol.h" #include "Clients.h" -#include "Reflector.h" - //////////////////////////////////////////////////////////////////////////////////////// // constructor @@ -38,12 +37,10 @@ CProtocol::~CProtocol() Close(); // empty queue - m_Queue.Lock(); - while ( !m_Queue.empty() ) + while ( !m_Queue.IsEmpty() ) { - m_Queue.pop(); + m_Queue.Pop(); } - m_Queue.Unlock(); } //////////////////////////////////////////////////////////////////////////////////////// @@ -51,6 +48,7 @@ CProtocol::~CProtocol() bool CProtocol::Initialize(const char *type, const EProtocol ptype, const uint16_t port, const bool has_ipv4, const bool has_ipv6) { + m_Port = port; // init reflector apparent callsign m_ReflectorCallsign = g_Reflector.GetCallsign(); @@ -62,10 +60,10 @@ bool CProtocol::Initialize(const char *type, const EProtocol ptype, const uint16 m_ReflectorCallsign.PatchCallsign(0, type, 3); // create our sockets -#ifdef LISTEN_IPV4 if (has_ipv4) { - CIp ip4(AF_INET, port, LISTEN_IPV4); + const std::string ipv4binding(g_Configure.GetString(g_Keys.ip.ipv4bind)); + CIp ip4(AF_INET, port, ipv4binding.c_str()); if ( ip4.IsSet() ) { if (! m_Socket4.Open(ip4)) @@ -73,25 +71,27 @@ bool CProtocol::Initialize(const char *type, const EProtocol ptype, const uint16 } std::cout << "Listening on " << ip4 << std::endl; } -#endif -#ifdef LISTEN_IPV6 - if (has_ipv6) + if (g_Configure.IsString(g_Keys.ip.ipv6bind)) { - CIp ip6(AF_INET6, port, LISTEN_IPV6); - if ( ip6.IsSet() ) + if (has_ipv6) { - if (! m_Socket6.Open(ip6)) + const std::string ipv6binding(g_Configure.GetString(g_Keys.ip.ipv6bind)); + CIp ip6(AF_INET6, port, ipv6binding.c_str()); + if ( ip6.IsSet() ) { - m_Socket4.Close(); - return false; + if (! m_Socket6.Open(ip6)) + { + m_Socket4.Close(); + return false; + } + std::cout << "Listening on " << ip6 << std::endl; } - std::cout << "Listening on " << ip6 << std::endl; } } -#endif - try { + try + { m_Future = std::async(std::launch::async, &CProtocol::Thread, this); } catch (const std::exception &e) @@ -136,9 +136,7 @@ void CProtocol::OnDvFramePacketIn(std::unique_ptr &Frame, const // set the packet module, the transcoder needs this Frame->SetPacketModule(stream->GetOwnerClient()->GetReflectorModule()); // and push - stream->Lock(); stream->Push(std::move(Frame)); - stream->Unlock(); } #ifdef DEBUG else @@ -174,18 +172,15 @@ void CProtocol::CheckStreamsTimeout(void) for ( auto it=m_Streams.begin(); it!=m_Streams.end(); ) { // time out ? - it->second->Lock(); if ( it->second->IsExpired() ) { // yes, close it - it->second->Unlock(); g_Reflector.CloseStream(it->second); // and remove it from the m_Streams map it = m_Streams.erase(it); } else { - it->second->Unlock(); it++; } } diff --git a/reflector/Protocol.h b/reflector/Protocol.h index 6d0c37c..678b533 100644 --- a/reflector/Protocol.h +++ b/reflector/Protocol.h @@ -71,17 +71,17 @@ public: virtual bool Initialize(const char *type, const EProtocol ptype, const uint16_t port, const bool has_ipv4, const bool has_ipv6); virtual void Close(void); - // queue - CPacketQueue *GetQueue(void) { m_Queue.Lock(); return &m_Queue; } - void ReleaseQueue(void) { m_Queue.Unlock(); } - // get const CCallsign &GetReflectorCallsign(void)const { return m_ReflectorCallsign; } + uint16_t GetPort(void) const { return m_Port; } // task void Thread(void); virtual void Task(void) = 0; + // pass-through + void Push(std::unique_ptr p) { m_Queue.Push(std::move(p)); } + protected: // stream helpers virtual void OnDvFramePacketIn(std::unique_ptr &, const CIp * = nullptr); @@ -114,6 +114,9 @@ protected: void Send(const CBuffer &buf, const CIp &Ip, uint16_t port) const; void Send(const char *buf, const CIp &Ip, uint16_t port) const; void Send(const SM17Frame &frame, const CIp &Ip) const; +#ifdef DEBUG + void Dump(const char *title, const uint8_t *data, int length); +#endif // socket CUdpSocket m_Socket4; @@ -123,7 +126,7 @@ protected: std::unordered_map> m_Streams; // queue - CPacketQueue m_Queue; + CSafePacketQueue> m_Queue; // thread std::atomic keep_running; @@ -132,10 +135,8 @@ protected: // identity CCallsign m_ReflectorCallsign; + // data + uint16_t m_Port; // debug CTimer m_DebugTimer; - -#ifdef DEBUG - void Dump(const char *title, const uint8_t *pointer, int length); -#endif }; diff --git a/reflector/Protocols.cpp b/reflector/Protocols.cpp index c2f10c1..5109b93 100644 --- a/reflector/Protocols.cpp +++ b/reflector/Protocols.cpp @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include "DExtraProtocol.h" #include "DPlusProtocol.h" #include "DCSProtocol.h" @@ -29,10 +29,9 @@ #include "P25Protocol.h" #include "NXDNProtocol.h" #include "USRPProtocol.h" -#ifndef NO_G3 #include "G3Protocol.h" -#endif #include "Protocols.h" +#include "Global.h" //////////////////////////////////////////////////////////////////////////////////////// // destructor @@ -50,58 +49,65 @@ bool CProtocols::Init(void) m_Mutex.lock(); { m_Protocols.emplace_back(std::unique_ptr(new CDextraProtocol)); - if (! m_Protocols.back()->Initialize("XRF", EProtocol::dextra, DEXTRA_PORT, DSTAR_IPV4, DSTAR_IPV6)) + if (! m_Protocols.back()->Initialize("XRF", EProtocol::dextra, uint16_t(g_Configure.GetUnsigned(g_Keys.dextra.port)), DSTAR_IPV4, DSTAR_IPV6)) return false; m_Protocols.emplace_back(std::unique_ptr(new CDplusProtocol)); - if (! m_Protocols.back()->Initialize("REF", EProtocol::dplus, DPLUS_PORT, DSTAR_IPV4, DSTAR_IPV6)) + if (! m_Protocols.back()->Initialize("REF", EProtocol::dplus, uint16_t(g_Configure.GetUnsigned(g_Keys.dplus.port)), DSTAR_IPV4, DSTAR_IPV6)) return false; m_Protocols.emplace_back(std::unique_ptr(new CDcsProtocol)); - if (! m_Protocols.back()->Initialize("DCS", EProtocol::dcs, DCS_PORT, DSTAR_IPV4, DSTAR_IPV6)) + if (! m_Protocols.back()->Initialize("DCS", EProtocol::dcs, uint16_t(g_Configure.GetUnsigned(g_Keys.dcs.port)), DSTAR_IPV4, DSTAR_IPV6)) return false; m_Protocols.emplace_back(std::unique_ptr(new CDmrmmdvmProtocol)); - if (! m_Protocols.back()->Initialize(nullptr, EProtocol::dmrmmdvm, DMRMMDVM_PORT, DMR_IPV4, DMR_IPV6)) + if (! m_Protocols.back()->Initialize(nullptr, EProtocol::dmrmmdvm, uint16_t(g_Configure.GetUnsigned(g_Keys.mmdvm.port)), DMR_IPV4, DMR_IPV6)) return false; - m_Protocols.emplace_back(std::unique_ptr(new CBMProtocol)); - if (! m_Protocols.back()->Initialize("XLX", EProtocol::bm, XLX_PORT, DMR_IPV4, DMR_IPV6)) - return false; + if (g_Configure.GetBoolean(g_Keys.bm.enable)) + { + m_Protocols.emplace_back(std::unique_ptr(new CBMProtocol)); + if (! m_Protocols.back()->Initialize("XLX", EProtocol::bm, uint16_t(g_Configure.GetUnsigned(g_Keys.bm.port)), DMR_IPV4, DMR_IPV6)) + return false; + } m_Protocols.emplace_back(std::unique_ptr(new CDmrplusProtocol)); - if (! m_Protocols.back()->Initialize(nullptr, EProtocol::dmrplus, DMRPLUS_PORT, DMR_IPV4, DMR_IPV6)) + if (! m_Protocols.back()->Initialize(nullptr, EProtocol::dmrplus, uint16_t(g_Configure.GetUnsigned(g_Keys.dmrplus.port)), DMR_IPV4, DMR_IPV6)) return false; m_Protocols.emplace_back(std::unique_ptr(new CYsfProtocol)); - if (! m_Protocols.back()->Initialize("YSF", EProtocol::ysf, YSF_PORT, YSF_IPV4, YSF_IPV6)) + if (! m_Protocols.back()->Initialize("YSF", EProtocol::ysf, uint16_t(g_Configure.GetUnsigned(g_Keys.ysf.port)), YSF_IPV4, YSF_IPV6)) return false; m_Protocols.emplace_back(std::unique_ptr(new CM17Protocol)); - if (! m_Protocols.back()->Initialize("URF", EProtocol::m17, M17_PORT, M17_IPV4, M17_IPV6)) + if (! m_Protocols.back()->Initialize("URF", EProtocol::m17, uint16_t(g_Configure.GetUnsigned(g_Keys.m17.port)), M17_IPV4, M17_IPV6)) return false; - + m_Protocols.emplace_back(std::unique_ptr(new CP25Protocol)); - if (! m_Protocols.back()->Initialize("P25", EProtocol::p25, P25_PORT, P25_IPV4, P25_IPV6)) + if (! m_Protocols.back()->Initialize("P25", EProtocol::p25, uint16_t(g_Configure.GetUnsigned(g_Keys.p25.port)), P25_IPV4, P25_IPV6)) return false; - + m_Protocols.emplace_back(std::unique_ptr(new CNXDNProtocol)); - if (! m_Protocols.back()->Initialize("NXDN", EProtocol::nxdn, NXDN_PORT, NXDN_IPV4, NXDN_IPV6)) - return false; - - m_Protocols.emplace_back(std::unique_ptr(new CUSRPProtocol)); - if (! m_Protocols.back()->Initialize("USRP", EProtocol::usrp, USRP_PORT, USRP_IPV4, USRP_IPV6)) + if (! m_Protocols.back()->Initialize("NXDN", EProtocol::nxdn, uint16_t(g_Configure.GetUnsigned(g_Keys.nxdn.port)), NXDN_IPV4, NXDN_IPV6)) return false; + if (g_Configure.GetBoolean(g_Keys.usrp.enable)) + { + m_Protocols.emplace_back(std::unique_ptr(new CUSRPProtocol)); + if (! m_Protocols.back()->Initialize("USRP", EProtocol::usrp, uint16_t(g_Configure.GetUnsigned(g_Keys.usrp.rxport)), USRP_IPV4, USRP_IPV6)) + return false; + } + m_Protocols.emplace_back(std::unique_ptr(new CURFProtocol)); - if (! m_Protocols.back()->Initialize("URF", EProtocol::urf, URF_PORT, URF_IPV4, URF_IPV6)) + if (! m_Protocols.back()->Initialize("URF", EProtocol::urf, uint16_t(g_Configure.GetUnsigned(g_Keys.urf.port)), URF_IPV4, URF_IPV6)) return false; -#ifndef NO_G3 - m_Protocols.emplace_back(std::unique_ptr(new CG3Protocol)); - if (! m_Protocols.back()->Initialize("XLX", EProtocol::g3, G3_DV_PORT, DMR_IPV4, DMR_IPV6)) + if (g_Configure.GetBoolean(g_Keys.g3.enable)) + { + m_Protocols.emplace_back(std::unique_ptr(new CG3Protocol)); + if (! m_Protocols.back()->Initialize("XLX", EProtocol::g3, G3_DV_PORT, DMR_IPV4, DMR_IPV6)) return false; -#endif + } } m_Mutex.unlock(); diff --git a/reflector/RawSocket.cpp b/reflector/RawSocket.cpp index c8ee654..615786f 100644 --- a/reflector/RawSocket.cpp +++ b/reflector/RawSocket.cpp @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include #include "Reflector.h" #include "RawSocket.h" diff --git a/reflector/Reflector.h b/reflector/Reflector.h index 4e7d233..efe22ea 100644 --- a/reflector/Reflector.h +++ b/reflector/Reflector.h @@ -18,6 +18,8 @@ #pragma once +#include + #include "Users.h" #include "Clients.h" #include "Peers.h" @@ -40,19 +42,8 @@ class CReflector { public: - // constructor - CReflector(); - // destructor - virtual ~CReflector(); - - // - const CCallsign &GetCallsign(void) const { return m_Callsign; } - -#ifdef TRANSCODER_IP - void SetTranscoderIp(const char *a, const int n) { memset(m_AmbedIp, 0, n); strncpy(m_AmbedIp, a, n-1); } - const char *GetTranscoderIp(void) const { return m_AmbedIp; } -#endif + ~CReflector(); // operation bool Start(void); @@ -71,7 +62,8 @@ public: bool IsStreaming(char); void CloseStream(std::shared_ptr); - // users + // get + const CCallsign &GetCallsign(void) const { return m_Callsign; } CUsers *GetUsers(void) { m_Users.Lock(); return &m_Users; } void ReleaseUsers(void) { m_Users.Unlock(); } @@ -90,9 +82,6 @@ protected: // threads void RouterThread(const char); void XmlReportThread(void); -#ifdef JSON_MONITOR - void JsonReportThread(void); -#endif // streams std::shared_ptr GetStream(char); @@ -101,23 +90,12 @@ protected: // xml helpers void WriteXmlFile(std::ofstream &); - -#ifdef JSON_MONITOR - // json helpers - void SendJsonReflectorObject(CUdpSocket &, CIp &); - void SendJsonNodesObject(CUdpSocket &, CIp &); - void SendJsonStationsObject(CUdpSocket &, CIp &); - void SendJsonOnairObject(CUdpSocket &, CIp &, const CCallsign &); - void SendJsonOffairObject(CUdpSocket &, CIp &, const CCallsign &); -#endif + void JsonReport(nlohmann::json &report); protected: // identity - const CCallsign m_Callsign; - const std::string m_Modules; -#ifdef TRANSCODER_IP - char m_AmbedIp[INET6_ADDRSTRLEN]; -#endif + CCallsign m_Callsign; + std::string m_Modules, m_TCmodules; // objects CUsers m_Users; // sorted list of lastheard stations @@ -127,22 +105,12 @@ protected: // queues std::unordered_map> m_Stream; -#ifdef TRANSCODED_MODULES - std::unordered_map> m_TCReader; -#endif // threads std::atomic keep_running; std::unordered_map> m_RouterFuture; std::future m_XmlReportFuture; -#ifdef JSON_MONITOR - std::future m_JsonReportFuture; -#endif + // notifications CNotificationQueue m_Notifications; - -public: -#ifdef DEBUG_DUMPFILE - std::ofstream m_DebugFile; -#endif }; diff --git a/reflector/SafePacketQueue.h b/reflector/SafePacketQueue.h new file mode 100644 index 0000000..fd51b40 --- /dev/null +++ b/reflector/SafePacketQueue.h @@ -0,0 +1,83 @@ +// urfd -- The universal reflector +// Copyright © 2023 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 3 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 . + +#pragma once + +#include +#include +#include + +/************************************************************ + * THIS IS IMPORTANT + * This template is primarly designed for std::unique_ptr! + * If you are going to use it for std::shared_ptr, then + * please consider that when you Push(), what you pushed + * from will be nullptr after the Push()! +\************************************************************/ + +template +class CSafePacketQueue +{ +public: + CSafePacketQueue(void) : q() , m() , c() {} + + ~CSafePacketQueue(void) {} + + void Push(T t) + { + std::lock_guard lock(m); + q.push(std::move(t)); + c.notify_one(); + } + + T Pop(void) + { + std::lock_guard lock(m); + if (q.empty()) + return nullptr; + else + { + T val = std::move(q.front()); + q.pop(); + return val; + } + } + + // If the queue is empty, wait until an element is avaiable. + T PopWait(void) + { + std::unique_lock lock(m); + while(q.empty()) + { + // release lock as long as the wait and reaquire it afterwards. + c.wait(lock); + } + T val = std::move(q.front()); + q.pop(); + return val; + } + + bool IsEmpty(void) + { + std::unique_lock lock(m); + return q.empty(); + } + +private: + std::queue q; + mutable std::mutex m; + std::condition_variable c; +}; diff --git a/reflector/Semaphore.cpp b/reflector/Semaphore.cpp index 00c20a6..76c4b84 100644 --- a/reflector/Semaphore.cpp +++ b/reflector/Semaphore.cpp @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include "Semaphore.h" //////////////////////////////////////////////////////////////////////////////////////// diff --git a/reflector/Semaphore.h b/reflector/Semaphore.h index d7356a4..e8f8dcf 100644 --- a/reflector/Semaphore.h +++ b/reflector/Semaphore.h @@ -18,7 +18,8 @@ #pragma once -#include "Main.h" +#include +#include class CSemaphore { @@ -39,6 +40,6 @@ protected: // data std::mutex m_Mutex; std::condition_variable m_Condition; - size_t m_Count; + std::size_t m_Count; }; diff --git a/reflector/UDPMsgSocket.cpp b/reflector/UDPMsgSocket.cpp index b2b0f29..5571a06 100644 --- a/reflector/UDPMsgSocket.cpp +++ b/reflector/UDPMsgSocket.cpp @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include #include "UDPMsgSocket.h" diff --git a/reflector/UDPSocket.cpp b/reflector/UDPSocket.cpp index 9787cff..1b078b6 100644 --- a/reflector/UDPSocket.cpp +++ b/reflector/UDPSocket.cpp @@ -17,7 +17,7 @@ // along with this program. If not, see . #include -#include "Main.h" + #include "UDPSocket.h" diff --git a/reflector/URFClient.cpp b/reflector/URFClient.cpp index b9605b1..dd9feee 100644 --- a/reflector/URFClient.cpp +++ b/reflector/URFClient.cpp @@ -17,7 +17,7 @@ // along with this program. If not, see . #include -#include "Main.h" + #include "URFClient.h" diff --git a/reflector/URFClient.h b/reflector/URFClient.h index d0a2a9b..5f43145 100644 --- a/reflector/URFClient.h +++ b/reflector/URFClient.h @@ -18,6 +18,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#include "Defines.h" #include "Client.h" class CURFClient : public CClient diff --git a/reflector/URFPeer.cpp b/reflector/URFPeer.cpp index 34747c1..fcf5df5 100644 --- a/reflector/URFPeer.cpp +++ b/reflector/URFPeer.cpp @@ -18,7 +18,7 @@ #include -#include "Main.h" + #include "Reflector.h" #include "URFPeer.h" #include "URFClient.h" diff --git a/reflector/URFProtocol.cpp b/reflector/URFProtocol.cpp index 0c5dfa7..673f21d 100644 --- a/reflector/URFProtocol.cpp +++ b/reflector/URFProtocol.cpp @@ -17,12 +17,12 @@ // along with this program. If not, see . #include -#include "Main.h" + #include "URFPeer.h" #include "URFProtocol.h" #include "Reflector.h" #include "GateKeeper.h" - +#include "Global.h" //////////////////////////////////////////////////////////////////////////////////////// // operation @@ -200,11 +200,10 @@ void CURFProtocol::Task(void) void CURFProtocol::HandleQueue(void) { - m_Queue.Lock(); - while ( !m_Queue.empty() ) + while (! m_Queue.IsEmpty()) { // get the packet - auto packet = m_Queue.pop(); + auto packet = m_Queue.Pop(); // check if origin of packet is local // if not, do not stream it out as it will cause @@ -236,7 +235,6 @@ void CURFProtocol::HandleQueue(void) } } } - m_Queue.Unlock(); } //////////////////////////////////////////////////////////////////////////////////////// @@ -324,7 +322,7 @@ void CURFProtocol::HandlePeerLinks(void) it->ResolveIp(); // send connect packet to re-initiate peer link EncodeConnectPacket(&buffer, it->GetModules()); - Send(buffer, it->GetIp(), URF_PORT); + Send(buffer, it->GetIp(), m_Port); std::cout << "Sending connect packet to URF peer " << cs << " @ " << it->GetIp() << " for modules " << it->GetModules() << std::endl; } } @@ -420,7 +418,7 @@ bool CURFProtocol::IsValidConnectPacket(const CBuffer &Buffer, CCallsign *callsi memcpy(modules, Buffer.data()+10, 27); for ( unsigned i = 0; i < strlen(modules); i++ ) { - valid &= (nullptr != strchr(ACTIVE_MODULES, modules[i])); + valid = valid && (g_Reflector.IsValidModule (modules[i])); } } return valid; @@ -450,7 +448,7 @@ bool CURFProtocol::IsValidAckPacket(const CBuffer &Buffer, CCallsign *callsign, memcpy(modules, Buffer.data()+10, 27); for ( unsigned i = 0; i < strlen(modules); i++ ) { - valid &= (nullptr != strchr(ACTIVE_MODULES, modules[i])); + valid = valid && (g_Reflector.IsValidModule(modules[i])); } } return valid; @@ -533,9 +531,9 @@ void CURFProtocol::EncodeConnectPacket(CBuffer *Buffer, const char *Modules) g_Reflector.GetCallsign().CodeOut(Buffer->data()+4); // our version Buffer->ReplaceAt(10, (uint8_t *)Modules, strlen(Modules)); - Buffer->Append((uint8_t)VERSION_MAJOR); - Buffer->Append((uint8_t)VERSION_MINOR); - Buffer->Append((uint8_t)VERSION_REVISION); + Buffer->Append((uint8_t)g_Version.GetMajor()); + Buffer->Append((uint8_t)g_Version.GetMinor()); + Buffer->Append((uint8_t)g_Version.GetRevision()); } void CURFProtocol::EncodeDisconnectPacket(CBuffer *Buffer) @@ -555,9 +553,9 @@ void CURFProtocol::EncodeConnectAckPacket(CBuffer *Buffer, const char *Modules) // the shared modules Buffer->ReplaceAt(10, (uint8_t *)Modules, strlen(Modules)); // our version - Buffer->Append((uint8_t)VERSION_MAJOR); - Buffer->Append((uint8_t)VERSION_MINOR); - Buffer->Append((uint8_t)VERSION_REVISION); + Buffer->Append((uint8_t)g_Version.GetMajor()); + Buffer->Append((uint8_t)g_Version.GetMinor()); + Buffer->Append((uint8_t)g_Version.GetRevision()); } void CURFProtocol::EncodeConnectNackPacket(CBuffer *Buffer) diff --git a/reflector/URFProtocol.h b/reflector/URFProtocol.h index 5418a0e..482d8e6 100644 --- a/reflector/URFProtocol.h +++ b/reflector/URFProtocol.h @@ -18,6 +18,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#include "Defines.h" #include "Version.h" #include "Timer.h" #include "SEProtocol.h" diff --git a/reflector/USRPClient.cpp b/reflector/USRPClient.cpp index f75c866..a94619f 100644 --- a/reflector/USRPClient.cpp +++ b/reflector/USRPClient.cpp @@ -17,7 +17,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include "USRPClient.h" diff --git a/reflector/USRPClient.h b/reflector/USRPClient.h index 2157d2b..bb5e307 100644 --- a/reflector/USRPClient.h +++ b/reflector/USRPClient.h @@ -19,7 +19,9 @@ #pragma once +#include "Defines.h" #include "Client.h" + class CUSRPClient : public CClient { public: diff --git a/reflector/USRPProtocol.h b/reflector/USRPProtocol.h index b4b2877..9420971 100644 --- a/reflector/USRPProtocol.h +++ b/reflector/USRPProtocol.h @@ -1,8 +1,5 @@ -// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. - // urfd -- The universal reflector -// Copyright © 2021 Thomas A. Early N7TAE -// Copyright © 2021 Doug McLain AD8DP +// Copyright © 2023 Doug McLain AD8DP // // 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 @@ -19,16 +16,14 @@ #pragma once +#include "Defines.h" #include "Timer.h" #include "Protocol.h" #include "DVHeaderPacket.h" #include "DVFramePacket.h" +#include "Timer.h" +#include "Clients.h" -//////////////////////////////////////////////////////////////////////////////////////// -// define - -#define USRP_MODULE_ID 'B' -//////////////////////////////////////////////////////////////////////////////////////// // class class CUSRPStreamCacheItem @@ -53,7 +48,7 @@ protected: // queue helper void HandleQueue(void); void HandleKeepalives(void); - + // stream helpers void OnDvHeaderPacketIn(std::unique_ptr &, const CIp &); @@ -72,4 +67,10 @@ protected: // for queue header caches std::unordered_map m_StreamsCache; uint32_t m_uiStreamId; + +private: + // CConfigure data + CCallsign m_Callsign; + char m_Module; + uint16_t m_txPort; }; diff --git a/reflector/User.cpp b/reflector/User.cpp index 105cd94..2110fdc 100644 --- a/reflector/User.cpp +++ b/reflector/User.cpp @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" +#include #include #include "User.h" @@ -77,23 +77,17 @@ void CUser::WriteXml(std::ofstream &xmlFile) xmlFile << "" << std::endl; } -void CUser::GetJsonObject(char *Buffer) +void CUser::JsonReport(nlohmann::json &report) { - char sz[512]; - char mbstr[100]; - char my[16]; - char rpt1[16]; - - if (std::strftime(mbstr, sizeof(mbstr), "%A %c", std::localtime(&m_LastHeardTime))) + nlohmann::json juser; + juser["Callsign"] = m_My.GetCS(); + juser["Repeater"] = m_Rpt1.GetCS(); + juser["OnModule"] = std::string(1, m_Rpt2.GetCSModule()); + juser["ViaPeer"] = m_Xlx.GetCS(); + char s[100]; + if (std::strftime(s, sizeof(s), "%FT%TZ", std::gmtime(&m_LastHeardTime))) { - m_My.GetCallsignString(my); - m_Rpt1.GetCallsignString(rpt1); - - ::sprintf(sz, "{\"callsign\":\"%s\",\"node\":\"%s\",\"module\":\"%c\",\"time\":\"%s\"}", - my, - rpt1, - m_Rpt1.GetCSModule(), - mbstr); - ::strcat(Buffer, sz); + juser["LastHeard"] = s; } + report["Users"].push_back(juser); } diff --git a/reflector/User.h b/reflector/User.h index c9869fa..d5ed1d2 100644 --- a/reflector/User.h +++ b/reflector/User.h @@ -18,6 +18,8 @@ #pragma once +#include + #include "Callsign.h" #include "Buffer.h" @@ -33,7 +35,7 @@ public: ~CUser() {} // operation - void HeardNow(void) { m_LastHeardTime = std::time(nullptr); } + void HeardNow(void) { m_LastHeardTime = time(nullptr); } // operators bool operator ==(const CUser &) const; @@ -41,7 +43,7 @@ public: // reporting void WriteXml(std::ofstream &); - void GetJsonObject(char *); + void JsonReport(nlohmann::json &report); protected: // data @@ -49,5 +51,5 @@ protected: CCallsign m_Rpt1; CCallsign m_Rpt2; CCallsign m_Xlx; - std::time_t m_LastHeardTime; + time_t m_LastHeardTime; }; diff --git a/reflector/Users.cpp b/reflector/Users.cpp index 1406e18..ac7d774 100644 --- a/reflector/Users.cpp +++ b/reflector/Users.cpp @@ -16,9 +16,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include "Users.h" -#include "Reflector.h" +#include "Global.h" //////////////////////////////////////////////////////////////////////////////////////// // constructor @@ -28,6 +28,7 @@ CUsers::CUsers() {} //////////////////////////////////////////////////////////////////////////////////////// // users management +#define LASTHEARD_USERS_MAX_SIZE 20 void CUsers::AddUser(const CUser &user) { // add diff --git a/reflector/Users.h b/reflector/Users.h index 6216269..f4e3343 100644 --- a/reflector/Users.h +++ b/reflector/Users.h @@ -18,6 +18,9 @@ #pragma once +#include +#include + #include "User.h" class CUsers diff --git a/reflector/Version.cpp b/reflector/Version.cpp index 96d296d..35d3c9d 100644 --- a/reflector/Version.cpp +++ b/reflector/Version.cpp @@ -1,73 +1,89 @@ -// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. - -// urfd -- The universal reflector -// Copyright © 2021 Thomas A. Early N7TAE +// Copyright © 2023 Thomas A. Early N7TAE. +// +// ---------------------------------------------------------------------------- +// This file is part of m17ref. // -// 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 3 of the License, or -// (at your option) any later version. +// m17ref 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 3 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. +// m17ref 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 . +// You should have received a copy of the GNU General Public License +// with this software. If not, see . +// ---------------------------------------------------------------------------- -#include "Main.h" #include "Version.h" -//////////////////////////////////////////////////////////////////////////////////////// -// constructor +CVersion::CVersion() : version(0) {} + +CVersion::CVersion(uint8_t maj, uint8_t min, uint8_t rev) +{ + Set(maj, min, rev); +} + +int CVersion::GetMajor(void) const +{ + return version / 0x10000; +} + +int CVersion::GetMinor(void) const +{ + return version / 0x100 % 0x100; +} + +int CVersion::GetRevision(void) const +{ + return version % 0x100; +} -CVersion::CVersion() +int CVersion::GetVersion(void) const { - m_iMajor = 0; - m_iMinor = 0; - m_iRevision = 0; + return version; } -CVersion::CVersion(int iMajor, int iMinor, int iRevision) +void CVersion::Set(uint8_t maj, uint8_t min, uint8_t rev) { - m_iMajor = iMajor; - m_iMinor = iMinor; - m_iRevision = iRevision; + version = 0x10000*maj + 0x100*min + rev; } -//////////////////////////////////////////////////////////////////////////////////////// -// comparaison +bool CVersion::operator ==(const CVersion &v) const +{ + return v.version == version; +}; + +bool CVersion::operator !=(const CVersion &v) const +{ + return v.version != version; +}; + +bool CVersion::operator >=(const CVersion &v) const +{ + return v.version >= version; +} -bool CVersion::IsEqualOrHigherTo(const CVersion &version) const +bool CVersion::operator <=(const CVersion &v) const { - if ( m_iMajor > version.m_iMajor ) - { - return true; - } - else if ( m_iMajor == version.m_iMajor ) - { - if ( m_iMinor > version.m_iMinor ) - { - return true; - } - else if ( m_iMinor == version.m_iMinor ) - { - if ( m_iRevision >= version.m_iRevision ) - { - return true; - } - } - } - return false; + return v.version <= version; } -//////////////////////////////////////////////////////////////////////////////////////// -// operator +bool CVersion::operator >(const CVersion &v) const +{ + return v.version > version; +} -bool CVersion::operator ==(const CVersion &Version) const +bool CVersion::operator <(const CVersion &v) const { - return ( (Version.m_iMajor == m_iMajor) && - (Version.m_iMinor == m_iMinor) && - (Version.m_iRevision == m_iRevision )) ; + return v.version < version; } + +// output +std::ostream &operator <<(std::ostream &os, const CVersion &v) +{ + os << v.GetMajor() << '.' << v.GetMinor() << '.' << v.GetRevision(); + return os; +}; diff --git a/reflector/Version.h b/reflector/Version.h index 256bfd8..84aa8d4 100644 --- a/reflector/Version.h +++ b/reflector/Version.h @@ -1,44 +1,56 @@ -// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved. - -// urfd -- The universal reflector -// Copyright © 2021 Thomas A. Early N7TAE +// Copyright © 2023 Thomas A. Early N7TAE. +// +// ---------------------------------------------------------------------------- +// This file is part of m17ref. // -// 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 3 of the License, or -// (at your option) any later version. +// m17ref 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 3 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. +// m17ref 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 . +// You should have received a copy of the GNU General Public License +// with this software. If not, see . +// ---------------------------------------------------------------------------- #pragma once +#include +#include + class CVersion { public: - // constructor + // constructors CVersion(); - CVersion(int, int, int); + CVersion(uint8_t maj, uint8_t min, uint8_t rev); // get - int GetMajor(void) const { return m_iMajor; } - int GetMinor(void) const { return m_iMinor; } - int GetRevision(void) const { return m_iRevision; } + int GetMajor(void) const; + int GetMinor(void) const; + int GetRevision(void) const; + int GetVersion(void) const; + + // set + void Set(uint8_t, uint8_t, uint8_t); + + // comparaison operators + bool operator ==(const CVersion &v) const; + bool operator !=(const CVersion &v) const; + bool operator >=(const CVersion &v) const; + bool operator <=(const CVersion &v) const; + bool operator >(const CVersion &v) const; + bool operator <(const CVersion &v) const; - // comparaison - bool IsEqualOrHigherTo(const CVersion &) const; + // output + friend std::ostream &operator <<(std::ostream &os, const CVersion &v); - // operator - bool operator ==(const CVersion &) const; protected: // data - int m_iMajor; - int m_iMinor; - int m_iRevision; + int version; }; diff --git a/reflector/WiresXCmd.cpp b/reflector/WiresXCmd.cpp index 225538f..d94616f 100644 --- a/reflector/WiresXCmd.cpp +++ b/reflector/WiresXCmd.cpp @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include "WiresXCmd.h" diff --git a/reflector/WiresXCmdHandler.cpp b/reflector/WiresXCmdHandler.cpp index ab1360e..195eca2 100644 --- a/reflector/WiresXCmdHandler.cpp +++ b/reflector/WiresXCmdHandler.cpp @@ -17,16 +17,14 @@ // along with this program. If not, see . #include -#include "Main.h" + #include "CRC.h" #include "YSFFich.h" #include "YSFPayload.h" #include "YSFClient.h" -#include "YSFNodeDirFile.h" -#include "YSFNodeDirHttp.h" #include "YSFUtils.h" -#include "Reflector.h" #include "WiresXCmdHandler.h" +#include "Global.h" //////////////////////////////////////////////////////////////////////////////////////// // constructor @@ -141,10 +139,9 @@ void CWiresxCmdHandler::Task(void) // handle it if ( bCmd ) { - const char *modules = ACTIVE_MODULES; // fill our info object Info = m_ReflectorWiresxInfo; - g_YsfNodeDir.FindFrequencies(Cmd.GetCallsign(), &uiNodeTxFreq, &uiNodeRxFreq); + g_LYtr.FindFrequencies(Cmd.GetCallsign(), uiNodeTxFreq, uiNodeRxFreq); Info.SetFrequencies(uiNodeTxFreq, uiNodeRxFreq); // find our client and the module it's currentlink linked to @@ -173,7 +170,7 @@ void CWiresxCmdHandler::Task(void) break; case WIRESX_CMD_CONN_REQ: cModule = 'A' + (char)(Cmd.GetArg() - 1); - if (::strchr(modules, cModule)) + if (g_Reflector.IsValidModule(cModule)) { std::cout << "Wires-X CONN_REQ command to link on module " << cModule << " from " << Cmd.GetCallsign() << " at " << Cmd.GetIp() << std::endl; // acknowledge @@ -337,8 +334,8 @@ bool CWiresxCmdHandler::ReplyToWiresxAllReqPacket(const CIp &Ip, const CWiresxIn memcpy(data + 12U, WiresxInfo.GetNode(), 10U); // number of entries - const char *modules = ACTIVE_MODULES; - uint NB_OF_MODULES = ::strlen(modules); + const std::string modules(g_Configure.GetString(g_Keys.modules.modules)); + uint NB_OF_MODULES = modules.size(); uint total = NB_OF_MODULES; uint n = NB_OF_MODULES - Start; if (n > 20U) diff --git a/reflector/WiresXInfo.cpp b/reflector/WiresXInfo.cpp index 3da1fb6..3069a32 100644 --- a/reflector/WiresXInfo.cpp +++ b/reflector/WiresXInfo.cpp @@ -17,11 +17,11 @@ // along with this program. If not, see . #include -#include "Main.h" + +#include "Defines.h" #include "Callsign.h" #include "WiresXInfo.h" - //////////////////////////////////////////////////////////////////////////////////////// // constructor diff --git a/reflector/YSFClient.cpp b/reflector/YSFClient.cpp index 58c3b47..7f6fc60 100644 --- a/reflector/YSFClient.cpp +++ b/reflector/YSFClient.cpp @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include "YSFClient.h" diff --git a/reflector/YSFClient.h b/reflector/YSFClient.h index 08ff034..9b662b4 100644 --- a/reflector/YSFClient.h +++ b/reflector/YSFClient.h @@ -18,7 +18,9 @@ #pragma once +#include "Defines.h" #include "Client.h" + class CYsfClient : public CClient { public: diff --git a/reflector/YSFNode.cpp b/reflector/YSFNode.cpp index 2b4d626..badd37b 100644 --- a/reflector/YSFNode.cpp +++ b/reflector/YSFNode.cpp @@ -17,7 +17,7 @@ // along with this program. If not, see . #include -#include "Main.h" + #include "YSFNode.h" diff --git a/reflector/YSFNode.h b/reflector/YSFNode.h index dd5ca81..202c940 100644 --- a/reflector/YSFNode.h +++ b/reflector/YSFNode.h @@ -18,7 +18,7 @@ #pragma once -#include "Main.h" +#include class CYsfNode { diff --git a/reflector/YSFNodeDir.cpp b/reflector/YSFNodeDir.cpp deleted file mode 100644 index 7f11b34..0000000 --- a/reflector/YSFNodeDir.cpp +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright © 2019 Jean-Luc Deltombe (LX3JL). All rights reserved. - -// urfd -- The universal reflector -// Copyright © 2021 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 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include -#include -#include "Main.h" -#include "Reflector.h" -#include "YSFNodeDir.h" - - -//////////////////////////////////////////////////////////////////////////////////////// -// constructor & destructor - -CYsfNodeDir::CYsfNodeDir() -{ - keep_running = true; -} - -CYsfNodeDir::~CYsfNodeDir() -{ - // kill threads - Close(); -} - - -//////////////////////////////////////////////////////////////////////////////////////// -// init & close - -bool CYsfNodeDir::Init(void) -{ - // load content - Reload(); - - // reset run flag - keep_running = true; - - // start thread; - m_Future = std::async(std::launch::async, &CYsfNodeDir::Thread, this); - - return true; -} - -void CYsfNodeDir::Close(void) -{ - keep_running = false; - if ( m_Future.valid() ) - { - m_Future.get(); - } -} - -//////////////////////////////////////////////////////////////////////////////////////// -// thread - -void CYsfNodeDir::Thread() -{ - while (keep_running) - { - // Wait YSFNODEDB_REFRESH_RATE minutes - for (int i=0; keep_running && (i < 30*YSFNODEDB_REFRESH_RATE); i++) - { - std::this_thread::sleep_for(std::chrono::milliseconds(2000)); -#if YSF_DB_SUPPORT==true - if (keep_running && (0 == i % 450)) - { - ReadDb(); // update from the db every 15 minutes - } -#endif - } - // have lists files changed ? - if (keep_running && NeedReload()) - { - Reload(); - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////// -// Reload - -bool CYsfNodeDir::Reload(void) -{ - CBuffer buffer; - bool ok = false; - - if ( LoadContent(&buffer) ) - { - Lock(); - ok = RefreshContent(buffer); - Unlock(); - } - return ok; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// find - -bool CYsfNodeDir::FindFrequencies(const CCallsign &callsign, uint32_t *txfreq, uint32_t *rxfreq) -{ - auto found = find(callsign); - if ( found != end() ) - { - *txfreq = found->second.GetTxFrequency(); - *rxfreq = found->second.GetRxFrequency(); - return true; - } - else - { - *txfreq = YSF_DEFAULT_NODE_TX_FREQ; - *rxfreq = YSF_DEFAULT_NODE_RX_FREQ; - return false; - } -} - -#if YSF_DB_SUPPORT==true -void CYsfNodeDir::ReadDb() -{ - MYSQL *con = mysql_init(NULL); - if (con) - { - if (mysql_real_connect(con, "localhost", YSF_DB_USER, YSF_DB_PASSWORD, YSF_DB_NAME, 0, NULL, 0)) - { - if (0 == mysql_query(con, "SELECT callsign,txfreq,rxfreq FROM ysfnodes")) - { - MYSQL_RES *result = mysql_store_result(con); - if (result) - { - std::cout << "Adding " << mysql_num_rows(result) << " registered YSF stations from database " << YSF_DB_NAME << std::endl; - MYSQL_ROW row; - - while ((row = mysql_fetch_row(result))) - { - CCallsign cs(row[0]); - CYsfNode node(atoi(row[1]), atoi(row[2])); - m_map[cs] = node; - } - - mysql_free_result(result); - } - else - { - std::cerr << "Could not fetch MySQL rows" << std::endl; - } - } - else - { - std::cerr << "MySQL query failed: " << mysql_error(con) << std::endl; - } - } - else - { - std::cerr << "Could not connect to database " << YSF_DB_NAME << ": " << mysql_error(con) << std::endl; - } - mysql_close(con); - } - else - { - std::cerr << "Could not init mysql." << std::endl;; - } -} -#endif diff --git a/reflector/YSFNodeDir.h b/reflector/YSFNodeDir.h deleted file mode 100644 index 26b5911..0000000 --- a/reflector/YSFNodeDir.h +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright © 2019 Jean-Luc Deltombe (LX3JL). All rights reserved. - -// urfd -- The universal reflector -// Copyright © 2021 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 3 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 . - -#pragma once - -#include -#include -#include -#include -#include "Buffer.h" -#include "Callsign.h" -#include "YSFNode.h" - -//////////////////////////////////////////////////////////////////////////////////////// -// compare function for std::map::find - -struct CYsfNodeDirCallsignCompare -{ - bool operator() (const CCallsign &cs1, const CCallsign &cs2) const - { return cs1.HasLowerCallsign(cs2);} -}; - -//////////////////////////////////////////////////////////////////////////////////////// -// class - -using CsNodeMap = std::map; - -class CYsfNodeDir -{ -public: - // constructor - CYsfNodeDir(); - // destructor - virtual ~CYsfNodeDir(); - - // init & close - virtual bool Init(void); - virtual void Close(void); - - // locks - void Lock(void) { m_Mutex.lock(); } - void Unlock(void) { m_Mutex.unlock(); } - - // refresh - virtual bool LoadContent(CBuffer *) { return false; } - virtual bool RefreshContent(const CBuffer &) { return false; } - - // find - bool FindFrequencies(const CCallsign &, uint32_t *, uint32_t *); - - // pass-thru - void clear() { m_map.clear(); } - size_t size() { return m_map.size(); } - CsNodeMap::iterator find(const CCallsign &cs) { return m_map.find(cs); } - CsNodeMap::iterator end() { return m_map.end(); } - std::pair insert(const std::pair &pair) { return m_map.insert(pair); } - -protected: - // thread - void Thread(); - - // reload helpers - bool Reload(void); - virtual bool NeedReload(void) { return false; } -#if YSF_DB_SUPPORT==true - void ReadDb(void); -#endif - //bool IsValidDmrid(const char *); - - -protected: - // Lock() - std::mutex m_Mutex; - - // thread - std::atomic keep_running; - std::future m_Future; - CsNodeMap m_map; -}; diff --git a/reflector/YSFNodeDirFile.cpp b/reflector/YSFNodeDirFile.cpp deleted file mode 100644 index 5879c5e..0000000 --- a/reflector/YSFNodeDirFile.cpp +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright © 2019 Jean-Luc Deltombe (LX3JL). All rights reserved. - -// urfd -- The universal reflector -// Copyright © 2021 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 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include -#include -#include -#include "Main.h" -#include "YSFNodeDirFile.h" - - -#if (YSFNODEDB_USE_RLX_SERVER == 0) -CYsfNodeDirFile g_YsfNodeDir; -#endif - -//////////////////////////////////////////////////////////////////////////////////////// -// constructor & destructor - -CYsfNodeDirFile::CYsfNodeDirFile() -{ - memset(&m_LastModTime, 0, sizeof(time_t)); -} - -//////////////////////////////////////////////////////////////////////////////////////// -// init & close - -bool CYsfNodeDirFile::Init(void) -{ - return CYsfNodeDir::Init(); -} - -//////////////////////////////////////////////////////////////////////////////////////// -// refresh - -bool CYsfNodeDirFile::NeedReload(void) -{ - bool needReload = false; - - time_t time; - if ( GetLastModTime(&time) ) - { - needReload = time != m_LastModTime; - } - return needReload; -} - -bool CYsfNodeDirFile::LoadContent(CBuffer *buffer) -{ - bool ok = false; - std::ifstream file; - std::streampos size; - - // open file - file.open(YSFNODEDB_PATH, std::ios::in | std::ios::binary | std::ios::ate); - if ( file.is_open() ) - { - // read file - size = file.tellg(); - if ( size > 0 ) - { - // read file into buffer - buffer->resize((int)size+1); - file.seekg (0, std::ios::beg); - file.read((char *)buffer->data(), (int)size); - - // close file - file.close(); - - // update time - GetLastModTime(&m_LastModTime); - - // done - ok = true; - } - } - - // done - return ok; -} - -bool CYsfNodeDirFile::RefreshContent(const CBuffer &buffer) -{ - bool ok = false; - - // clear directory - clear(); - - // scan buffer - if ( buffer.size() > 0 ) - { - // crack it - char *ptr1 = (char *)buffer.data(); - char *ptr2; - - // get next line - while ( (ptr2 = ::strchr(ptr1, '\n')) != nullptr ) - { - *ptr2 = 0; - // get items - char *callsign; - char *txfreq; - char *rxfreq; - if ( ((callsign = ::strtok(ptr1, ";")) != nullptr) ) - { - if ( ((txfreq = ::strtok(nullptr, ";")) != nullptr) ) - { - if ( ((rxfreq = ::strtok(nullptr, ";")) != nullptr) ) - { - // new entry - CCallsign cs(callsign); - CYsfNode node(atoi(txfreq), atoi(rxfreq)); - if ( cs.IsValid() && node.IsValid() ) - { - insert(std::pair(cs, node)); - } - } - } - } - // next line - ptr1 = ptr2+1; - } - - // done - ok = true; - } - - - // report - std::cout << "Read " << size() << " YSF nodes from file " << YSFNODEDB_PATH << std::endl; - - // done - return ok; -} - - -bool CYsfNodeDirFile::GetLastModTime(time_t *time) -{ - bool ok = false; - - struct stat fileStat; - if( ::stat(YSFNODEDB_PATH, &fileStat) != -1 ) - { - *time = fileStat.st_mtime; - ok = true; - } - return ok; -} diff --git a/reflector/YSFNodeDirFile.h b/reflector/YSFNodeDirFile.h deleted file mode 100644 index 8b15993..0000000 --- a/reflector/YSFNodeDirFile.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright © 2019 Jean-Luc Deltombe (LX3JL). All rights reserved. - -// urfd -- The universal reflector -// Copyright © 2021 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 3 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 . - -#pragma once - -#include "YSFNodeDir.h" - -class CYsfNodeDirFile : public CYsfNodeDir -{ -public: - // constructor - CYsfNodeDirFile(); - - // destructor - ~CYsfNodeDirFile() {} - - // init & close - bool Init(void); - - // refresh - bool LoadContent(CBuffer *); - bool RefreshContent(const CBuffer &); - -protected: - // reload helpers - bool NeedReload(void); - bool GetLastModTime(time_t *); - -protected: - // data - time_t m_LastModTime; -}; diff --git a/reflector/YSFNodeDirHttp.cpp b/reflector/YSFNodeDirHttp.cpp deleted file mode 100644 index f5fb8f3..0000000 --- a/reflector/YSFNodeDirHttp.cpp +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright © 2019 Jean-Luc Deltombe (LX3JL). All rights reserved. - -// urfd -- The universal reflector -// Copyright © 2021 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 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include -#include "Main.h" -#include "Reflector.h" -#include "YSFNodeDirHttp.h" - -#if (YSFNODEDB_USE_RLX_SERVER == 1) -CYsfNodeDirHttp g_YsfNodeDir; -#endif - - -//////////////////////////////////////////////////////////////////////////////////////// -// refresh - -bool CYsfNodeDirHttp::LoadContent(CBuffer *buffer) -{ - // get file from xlxapi server - return HttpGet("xlxapi.rlx.lu", "api/exportysfrepeaters.php", 80, buffer); -} - -bool CYsfNodeDirHttp::RefreshContent(const CBuffer &buffer) -{ - bool ok = false; - - // clear directory - clear(); - - // scan buffer - if ( buffer.size() > 0 ) - { - // crack it - char *ptr1 = (char *)buffer.data(); - char *ptr2; - - // get next line - while ( (ptr2 = ::strchr(ptr1, '\n')) != nullptr ) - { - *ptr2 = 0; - // get items - char *callsign; - char *txfreq; - char *rxfreq; - if ( ((callsign = ::strtok(ptr1, ";")) != nullptr) ) - { - if ( ((txfreq = ::strtok(nullptr, ";")) != nullptr) ) - { - if ( ((rxfreq = ::strtok(nullptr, ";")) != nullptr) ) - { - // new entry - CCallsign cs(callsign); - CYsfNode node(atoi(txfreq), atoi(rxfreq)); - if ( cs.IsValid() && node.IsValid() ) - { - insert(std::pair(cs, node)); - } - } - } - } - // next line - ptr1 = ptr2+1; - } - - // done - ok = true; - } - - // report - std::cout << "Read " << size() << " YSF nodes from xlxapi.rlx.lu database " << std::endl; - - // done - return ok; -} - - -//////////////////////////////////////////////////////////////////////////////////////// -// httpd helpers - -#define YSFNODE_HTTPGET_SIZEMAX (256) - -bool CYsfNodeDirHttp::HttpGet(const char *hostname, const char *filename, int port, CBuffer *buffer) -{ - bool ok = false; - int sock_id; - - // open socket - if ( (sock_id = ::socket(AF_INET, SOCK_STREAM, 0)) >= 0 ) - { - // get hostname address - struct sockaddr_in servaddr; - struct hostent *hp; - memset(&servaddr,0,sizeof(servaddr)); - if( (hp = gethostbyname(hostname)) != nullptr ) - { - // dns resolved - memcpy((char *)&servaddr.sin_addr.s_addr, (char *)hp->h_addr, hp->h_length); - servaddr.sin_port = htons(port); - servaddr.sin_family = AF_INET; - - // connect - if ( ::connect(sock_id, (struct sockaddr *)&servaddr, sizeof(servaddr)) == 0) - { - // send the GET request - char request[YSFNODE_HTTPGET_SIZEMAX]; - ::sprintf(request, "GET /%s HTTP/1.0\r\nFrom: %s\r\nUser-Agent: urfd\r\n\r\n", - filename, (const char *)g_Reflector.GetCallsign()); - ::write(sock_id, request, strlen(request)); - - // config receive timeouts - fd_set read_set; - struct timeval timeout; - timeout.tv_sec = 5; - timeout.tv_usec = 0; - FD_ZERO(&read_set); - FD_SET(sock_id, &read_set); - - // get the reply back - buffer->clear(); - bool done = false; - do - { - char buf[1440]; - ssize_t len = 0; - select(sock_id+1, &read_set, nullptr, nullptr, &timeout); - //if ( (ret > 0) || ((ret < 0) && (errno == EINPROGRESS)) ) - //if ( ret >= 0 ) - //{ - usleep(5000); - len = read(sock_id, buf, 1440); - if ( len > 0 ) - { - buffer->Append((uint8_t *)buf, (int)len); - ok = true; - } - //} - done = (len <= 0); - - } - while (!done); - buffer->Append((uint8_t)0); - - // and disconnect - close(sock_id); - } - else - { - std::cout << "Cannot establish connection with host " << hostname << std::endl; - } - } - else - { - std::cout << "Host " << hostname << " not found" << std::endl; - } - - } - else - { - std::cout << "Failed to open wget socket" << std::endl; - } - - // done - return ok; -} diff --git a/reflector/YSFProtocol.cpp b/reflector/YSFProtocol.cpp index 462f691..1a40ba4 100644 --- a/reflector/YSFProtocol.cpp +++ b/reflector/YSFProtocol.cpp @@ -16,17 +16,17 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" + #include #include "CRC.h" #include "YSFPayload.h" #include "YSFClient.h" -#include "YSFNodeDirFile.h" -#include "YSFNodeDirHttp.h" #include "YSFUtils.h" #include "YSFProtocol.h" -#include "Reflector.h" -#include "GateKeeper.h" +#include "Global.h" + +#define REG_NAME_SIZE 16 +#define REG_DESC_SIZE 14 //////////////////////////////////////////////////////////////////////////////////////// // constructor @@ -40,6 +40,14 @@ CYsfProtocol::CYsfProtocol() bool CYsfProtocol::Initialize(const char *type, const EProtocol ptype, const uint16_t port, const bool has_ipv4, const bool has_ipv6) { + // config data + m_AutolinkModule = g_Configure.GetAutolinkModule(g_Keys.ysf.autolinkmod); + m_RegistrationId = g_Configure.GetUnsigned(g_Keys.ysf.ysfreflectordb.id); + m_RegistrationName.assign(g_Configure.GetString(g_Keys.ysf.ysfreflectordb.name)); + m_RegistrationDesc.assign(g_Configure.GetString(g_Keys.ysf.ysfreflectordb.description)); + m_RegistrationName.resize(REG_NAME_SIZE, ' '); + m_RegistrationDesc.resize(REG_DESC_SIZE, ' '); + // base class if (! CProtocol::Initialize(type, ptype, port, has_ipv4, has_ipv6)) return false; @@ -156,9 +164,8 @@ void CYsfProtocol::Task(void) auto newclient = std::make_shared(Callsign, Ip); // aautolink, if enabled -#if YSF_AUTOLINK_ENABLE - newclient->SetReflectorModule(YSF_AUTOLINK_MODULE); -#endif + if (m_AutolinkModule) + newclient->SetReflectorModule(m_AutolinkModule); // and append clients->AddClient(newclient); @@ -297,11 +304,10 @@ void CYsfProtocol::OnDvHeaderPacketIn(std::unique_ptr &Header, void CYsfProtocol::HandleQueue(void) { - m_Queue.Lock(); - while ( !m_Queue.empty() ) + while (! m_Queue.IsEmpty()) { // get the packet - auto packet = m_Queue.pop(); + auto packet = m_Queue.Pop(); // get our sender's id const auto mod = packet->GetPacketModule(); @@ -362,7 +368,6 @@ void CYsfProtocol::HandleQueue(void) g_Reflector.ReleaseClients(); } } - m_Queue.Unlock(); } //////////////////////////////////////////////////////////////////////////////////////// @@ -506,7 +511,7 @@ bool CYsfProtocol::IsValidDvFramePacket(const CIp &Ip, const CYSFFICH &Fich, con { // get stream id //uint32_t uiStreamId = IpToStreamId(Ip); - + auto stream = GetStream(m_uiStreamId, &Ip); if ( !stream ) { @@ -531,10 +536,10 @@ bool CYsfProtocol::IsValidDvFramePacket(const CIp &Ip, const CYSFFICH &Fich, con CCallsign rpt2 = m_ReflectorCallsign; rpt2.SetCSModule(' '); header = std::unique_ptr(new CDvHeaderPacket(csMY, CCallsign("CQCQCQ"), rpt1, rpt2, m_uiStreamId, Fich.getFN())); - + if ( g_GateKeeper.MayTransmit(header->GetMyCallsign(), Ip, EProtocol::ysf, header->GetRpt2Module()) ) { - OnDvHeaderPacketIn(header, Ip); + OnDvHeaderPacketIn(header, Ip); } } @@ -983,32 +988,20 @@ bool CYsfProtocol::IsValidOptionsPacket(const CBuffer &Buffer) const bool CYsfProtocol::EncodeServerStatusPacket(CBuffer *Buffer) const { uint8_t tag[] = { 'Y','S','F','S' }; - uint8_t description[14]; - uint8_t callsign[16]; -#ifdef YSF_REFLECTOR_DESCRIPTION - const std::string desc = YSF_REFLECTOR_DESCRIPTION; -#else - const std::string desc("URF Reflector"); -#endif + uint8_t description[REG_DESC_SIZE]; + uint8_t callsign[REG_NAME_SIZE]; // tag Buffer->Set(tag, sizeof(tag)); // hash - memset(callsign, ' ', sizeof(callsign)); -#ifdef YSF_REFLECTOR_NAME - const std::string cs = YSF_REFLECTOR_NAME; - memcpy(callsign, cs.c_str(), cs.size() > 16 ? 16 : cs.size()); -#else - g_Reflector.GetCallsign().GetCallsign(callsign); -#endif + memcpy(callsign, m_RegistrationName.c_str(), 16); char sz[16]; - ::sprintf(sz, "%05u", CalcHash(callsign, 16) % 100000U); + ::sprintf(sz, "%05u", CalcHash(callsign, REG_NAME_SIZE) % 100000U); Buffer->Append((uint8_t *)sz, 5); // name - Buffer->Append(callsign, 16); + Buffer->Append(callsign, REG_NAME_SIZE); // description - memset(description, ' ', sizeof(description)); - memcpy(description, desc.c_str(), desc.size() > 14 ? 14 : desc.size()); - Buffer->Append(description, 14); + memcpy(description, m_RegistrationDesc.c_str(), REG_DESC_SIZE); + Buffer->Append(description, REG_DESC_SIZE); // connected clients CClients *clients = g_Reflector.GetClients(); int count = MIN(999, clients->GetSize()); @@ -1022,10 +1015,7 @@ bool CYsfProtocol::EncodeServerStatusPacket(CBuffer *Buffer) const uint32_t CYsfProtocol::CalcHash(const uint8_t *buffer, int len) const { -#ifdef YSF_REFLECTOR_ID - uint32_t hash = YSF_REFLECTOR_ID; -#else - uint32_t hash = 0U; + uint32_t hash = m_RegistrationId; for ( int i = 0; i < len; i++) { @@ -1033,7 +1023,6 @@ uint32_t CYsfProtocol::CalcHash(const uint8_t *buffer, int len) const hash += (hash << 10); hash ^= (hash >> 6); } -#endif hash += (hash << 3); hash ^= (hash >> 11); diff --git a/reflector/YSFProtocol.h b/reflector/YSFProtocol.h index 6e0534d..4c30da9 100644 --- a/reflector/YSFProtocol.h +++ b/reflector/YSFProtocol.h @@ -18,6 +18,7 @@ #pragma once +#include "Defines.h" #include "Timer.h" #include "Protocol.h" #include "DVHeaderPacket.h" @@ -129,4 +130,9 @@ protected: CWiresxCmdHandler m_WiresxCmdHandler; unsigned char m_seqNo; uint32_t m_uiStreamId; + + // config data + char m_AutolinkModule; + unsigned m_RegistrationId; + std::string m_RegistrationName, m_RegistrationDesc; }; diff --git a/reflector/YSFUtils.cpp b/reflector/YSFUtils.cpp index a3788ce..b47cf8d 100644 --- a/reflector/YSFUtils.cpp +++ b/reflector/YSFUtils.cpp @@ -19,20 +19,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "Main.h" #include #include "YSFDefines.h" #include "YSFUtils.h" #include "Golay24128.h" -//////////////////////////////////////////////////////////////////////////////////////// -// constants & defines - - - -//////////////////////////////////////////////////////////////////////////////////////// -// - void CYsfUtils::DecodeVD2Vchs(uint8_t *data, uint8_t **ambe) { int frame = 0; diff --git a/reflector/YSFUtils.h b/reflector/YSFUtils.h index 6805c66..6191a7f 100644 --- a/reflector/YSFUtils.h +++ b/reflector/YSFUtils.h @@ -21,6 +21,8 @@ #pragma once +#include + //////////////////////////////////////////////////////////////////////////////////////// // class diff --git a/wiresx/bootstrap.css b/wiresx/bootstrap.css deleted file mode 100644 index 6167622..0000000 --- a/wiresx/bootstrap.css +++ /dev/null @@ -1,6757 +0,0 @@ -/*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ -/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ -html { - font-family: sans-serif; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; -} -body { - margin: 0; -} -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -menu, -nav, -section, -summary { - display: block; -} -audio, -canvas, -progress, -video { - display: inline-block; - vertical-align: baseline; -} -audio:not([controls]) { - display: none; - height: 0; -} -[hidden], -template { - display: none; -} -a { - background-color: transparent; -} -a:active, -a:hover { - outline: 0; -} -abbr[title] { - border-bottom: 1px dotted; -} -b, -strong { - font-weight: bold; -} -dfn { - font-style: italic; -} -h1 { - margin: .67em 0; - font-size: 2em; -} -mark { - color: #000; - background: #ff0; -} -small { - font-size: 80%; -} -sub, -sup { - position: relative; - font-size: 75%; - line-height: 0; - vertical-align: baseline; -} -sup { - top: -.5em; -} -sub { - bottom: -.25em; -} -img { - border: 0; -} -svg:not(:root) { - overflow: hidden; -} -figure { - margin: 1em 40px; -} -hr { - height: 0; - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; -} -pre { - overflow: auto; -} -code, -kbd, -pre, -samp { - font-family: monospace, monospace; - font-size: 1em; -} -button, -input, -optgroup, -select, -textarea { - margin: 0; - font: inherit; - color: inherit; -} -button { - overflow: visible; -} -button, -select { - text-transform: none; -} -button, -html input[type="button"], -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; - cursor: pointer; -} -button[disabled], -html input[disabled] { - cursor: default; -} -button::-moz-focus-inner, -input::-moz-focus-inner { - padding: 0; - border: 0; -} -input { - line-height: normal; -} -input[type="checkbox"], -input[type="radio"] { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - padding: 0; -} -input[type="number"]::-webkit-inner-spin-button, -input[type="number"]::-webkit-outer-spin-button { - height: auto; -} -input[type="search"] { - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; - -webkit-appearance: textfield; -} -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} -fieldset { - padding: .35em .625em .75em; - margin: 0 2px; - border: 1px solid #c0c0c0; -} -legend { - padding: 0; - border: 0; -} -textarea { - overflow: auto; -} -optgroup { - font-weight: bold; -} -table { - border-spacing: 0; - border-collapse: collapse; -} -td, -th { - padding: 0; -} -/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ -@media print { - *, - *:before, - *:after { - color: #000 !important; - text-shadow: none !important; - background: transparent !important; - -webkit-box-shadow: none !important; - box-shadow: none !important; - } - a, - a:visited { - text-decoration: underline; - } - a[href]:after { - content: " (" attr(href) ")"; - } - abbr[title]:after { - content: " (" attr(title) ")"; - } - a[href^="#"]:after, - a[href^="javascript:"]:after { - content: ""; - } - pre, - blockquote { - border: 1px solid #999; - - page-break-inside: avoid; - } - thead { - display: table-header-group; - } - tr, - img { - page-break-inside: avoid; - } - img { - max-width: 100% !important; - } - p, - h2, - h3 { - orphans: 3; - widows: 3; - } - h2, - h3 { - page-break-after: avoid; - } - .navbar { - display: none; - } - .btn > .caret, - .dropup > .btn > .caret { - border-top-color: #000 !important; - } - .label { - border: 1px solid #000; - } - .table { - border-collapse: collapse !important; - } - .table td, - .table th { - background-color: #fff !important; - } - .table-bordered th, - .table-bordered td { - border: 1px solid #ddd !important; - } -} -@font-face { - font-family: 'Glyphicons Halflings'; - - src: url('../fonts/glyphicons-halflings-regular.eot'); - src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); -} -.glyphicon { - position: relative; - top: 1px; - display: inline-block; - font-family: 'Glyphicons Halflings'; - font-style: normal; - font-weight: normal; - line-height: 1; - - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} -.glyphicon-asterisk:before { - content: "\002a"; -} -.glyphicon-plus:before { - content: "\002b"; -} -.glyphicon-euro:before, -.glyphicon-eur:before { - content: "\20ac"; -} -.glyphicon-minus:before { - content: "\2212"; -} -.glyphicon-cloud:before { - content: "\2601"; -} -.glyphicon-envelope:before { - content: "\2709"; -} -.glyphicon-pencil:before { - content: "\270f"; -} -.glyphicon-glass:before { - content: "\e001"; -} -.glyphicon-music:before { - content: "\e002"; -} -.glyphicon-search:before { - content: "\e003"; -} -.glyphicon-heart:before { - content: "\e005"; -} -.glyphicon-star:before { - content: "\e006"; -} -.glyphicon-star-empty:before { - content: "\e007"; -} -.glyphicon-user:before { - content: "\e008"; -} -.glyphicon-film:before { - content: "\e009"; -} -.glyphicon-th-large:before { - content: "\e010"; -} -.glyphicon-th:before { - content: "\e011"; -} -.glyphicon-th-list:before { - content: "\e012"; -} -.glyphicon-ok:before { - content: "\e013"; -} -.glyphicon-remove:before { - content: "\e014"; -} -.glyphicon-zoom-in:before { - content: "\e015"; -} -.glyphicon-zoom-out:before { - content: "\e016"; -} -.glyphicon-off:before { - content: "\e017"; -} -.glyphicon-signal:before { - content: "\e018"; -} -.glyphicon-cog:before { - content: "\e019"; -} -.glyphicon-trash:before { - content: "\e020"; -} -.glyphicon-home:before { - content: "\e021"; -} -.glyphicon-file:before { - content: "\e022"; -} -.glyphicon-time:before { - content: "\e023"; -} -.glyphicon-road:before { - content: "\e024"; -} -.glyphicon-download-alt:before { - content: "\e025"; -} -.glyphicon-download:before { - content: "\e026"; -} -.glyphicon-upload:before { - content: "\e027"; -} -.glyphicon-inbox:before { - content: "\e028"; -} -.glyphicon-play-circle:before { - content: "\e029"; -} -.glyphicon-repeat:before { - content: "\e030"; -} -.glyphicon-refresh:before { - content: "\e031"; -} -.glyphicon-list-alt:before { - content: "\e032"; -} -.glyphicon-lock:before { - content: "\e033"; -} -.glyphicon-flag:before { - content: "\e034"; -} -.glyphicon-headphones:before { - content: "\e035"; -} -.glyphicon-volume-off:before { - content: "\e036"; -} -.glyphicon-volume-down:before { - content: "\e037"; -} -.glyphicon-volume-up:before { - content: "\e038"; -} -.glyphicon-qrcode:before { - content: "\e039"; -} -.glyphicon-barcode:before { - content: "\e040"; -} -.glyphicon-tag:before { - content: "\e041"; -} -.glyphicon-tags:before { - content: "\e042"; -} -.glyphicon-book:before { - content: "\e043"; -} -.glyphicon-bookmark:before { - content: "\e044"; -} -.glyphicon-print:before { - content: "\e045"; -} -.glyphicon-camera:before { - content: "\e046"; -} -.glyphicon-font:before { - content: "\e047"; -} -.glyphicon-bold:before { - content: "\e048"; -} -.glyphicon-italic:before { - content: "\e049"; -} -.glyphicon-text-height:before { - content: "\e050"; -} -.glyphicon-text-width:before { - content: "\e051"; -} -.glyphicon-align-left:before { - content: "\e052"; -} -.glyphicon-align-center:before { - content: "\e053"; -} -.glyphicon-align-right:before { - content: "\e054"; -} -.glyphicon-align-justify:before { - content: "\e055"; -} -.glyphicon-list:before { - content: "\e056"; -} -.glyphicon-indent-left:before { - content: "\e057"; -} -.glyphicon-indent-right:before { - content: "\e058"; -} -.glyphicon-facetime-video:before { - content: "\e059"; -} -.glyphicon-picture:before { - content: "\e060"; -} -.glyphicon-map-marker:before { - content: "\e062"; -} -.glyphicon-adjust:before { - content: "\e063"; -} -.glyphicon-tint:before { - content: "\e064"; -} -.glyphicon-edit:before { - content: "\e065"; -} -.glyphicon-share:before { - content: "\e066"; -} -.glyphicon-check:before { - content: "\e067"; -} -.glyphicon-move:before { - content: "\e068"; -} -.glyphicon-step-backward:before { - content: "\e069"; -} -.glyphicon-fast-backward:before { - content: "\e070"; -} -.glyphicon-backward:before { - content: "\e071"; -} -.glyphicon-play:before { - content: "\e072"; -} -.glyphicon-pause:before { - content: "\e073"; -} -.glyphicon-stop:before { - content: "\e074"; -} -.glyphicon-forward:before { - content: "\e075"; -} -.glyphicon-fast-forward:before { - content: "\e076"; -} -.glyphicon-step-forward:before { - content: "\e077"; -} -.glyphicon-eject:before { - content: "\e078"; -} -.glyphicon-chevron-left:before { - content: "\e079"; -} -.glyphicon-chevron-right:before { - content: "\e080"; -} -.glyphicon-plus-sign:before { - content: "\e081"; -} -.glyphicon-minus-sign:before { - content: "\e082"; -} -.glyphicon-remove-sign:before { - content: "\e083"; -} -.glyphicon-ok-sign:before { - content: "\e084"; -} -.glyphicon-question-sign:before { - content: "\e085"; -} -.glyphicon-info-sign:before { - content: "\e086"; -} -.glyphicon-screenshot:before { - content: "\e087"; -} -.glyphicon-remove-circle:before { - content: "\e088"; -} -.glyphicon-ok-circle:before { - content: "\e089"; -} -.glyphicon-ban-circle:before { - content: "\e090"; -} -.glyphicon-arrow-left:before { - content: "\e091"; -} -.glyphicon-arrow-right:before { - content: "\e092"; -} -.glyphicon-arrow-up:before { - content: "\e093"; -} -.glyphicon-arrow-down:before { - content: "\e094"; -} -.glyphicon-share-alt:before { - content: "\e095"; -} -.glyphicon-resize-full:before { - content: "\e096"; -} -.glyphicon-resize-small:before { - content: "\e097"; -} -.glyphicon-exclamation-sign:before { - content: "\e101"; -} -.glyphicon-gift:before { - content: "\e102"; -} -.glyphicon-leaf:before { - content: "\e103"; -} -.glyphicon-fire:before { - content: "\e104"; -} -.glyphicon-eye-open:before { - content: "\e105"; -} -.glyphicon-eye-close:before { - content: "\e106"; -} -.glyphicon-warning-sign:before { - content: "\e107"; -} -.glyphicon-plane:before { - content: "\e108"; -} -.glyphicon-calendar:before { - content: "\e109"; -} -.glyphicon-random:before { - content: "\e110"; -} -.glyphicon-comment:before { - content: "\e111"; -} -.glyphicon-magnet:before { - content: "\e112"; -} -.glyphicon-chevron-up:before { - content: "\e113"; -} -.glyphicon-chevron-down:before { - content: "\e114"; -} -.glyphicon-retweet:before { - content: "\e115"; -} -.glyphicon-shopping-cart:before { - content: "\e116"; -} -.glyphicon-folder-close:before { - content: "\e117"; -} -.glyphicon-folder-open:before { - content: "\e118"; -} -.glyphicon-resize-vertical:before { - content: "\e119"; -} -.glyphicon-resize-horizontal:before { - content: "\e120"; -} -.glyphicon-hdd:before { - content: "\e121"; -} -.glyphicon-bullhorn:before { - content: "\e122"; -} -.glyphicon-bell:before { - content: "\e123"; -} -.glyphicon-certificate:before { - content: "\e124"; -} -.glyphicon-thumbs-up:before { - content: "\e125"; -} -.glyphicon-thumbs-down:before { - content: "\e126"; -} -.glyphicon-hand-right:before { - content: "\e127"; -} -.glyphicon-hand-left:before { - content: "\e128"; -} -.glyphicon-hand-up:before { - content: "\e129"; -} -.glyphicon-hand-down:before { - content: "\e130"; -} -.glyphicon-circle-arrow-right:before { - content: "\e131"; -} -.glyphicon-circle-arrow-left:before { - content: "\e132"; -} -.glyphicon-circle-arrow-up:before { - content: "\e133"; -} -.glyphicon-circle-arrow-down:before { - content: "\e134"; -} -.glyphicon-globe:before { - content: "\e135"; -} -.glyphicon-wrench:before { - content: "\e136"; -} -.glyphicon-tasks:before { - content: "\e137"; -} -.glyphicon-filter:before { - content: "\e138"; -} -.glyphicon-briefcase:before { - content: "\e139"; -} -.glyphicon-fullscreen:before { - content: "\e140"; -} -.glyphicon-dashboard:before { - content: "\e141"; -} -.glyphicon-paperclip:before { - content: "\e142"; -} -.glyphicon-heart-empty:before { - content: "\e143"; -} -.glyphicon-link:before { - content: "\e144"; -} -.glyphicon-phone:before { - content: "\e145"; -} -.glyphicon-pushpin:before { - content: "\e146"; -} -.glyphicon-usd:before { - content: "\e148"; -} -.glyphicon-gbp:before { - content: "\e149"; -} -.glyphicon-sort:before { - content: "\e150"; -} -.glyphicon-sort-by-alphabet:before { - content: "\e151"; -} -.glyphicon-sort-by-alphabet-alt:before { - content: "\e152"; -} -.glyphicon-sort-by-order:before { - content: "\e153"; -} -.glyphicon-sort-by-order-alt:before { - content: "\e154"; -} -.glyphicon-sort-by-attributes:before { - content: "\e155"; -} -.glyphicon-sort-by-attributes-alt:before { - content: "\e156"; -} -.glyphicon-unchecked:before { - content: "\e157"; -} -.glyphicon-expand:before { - content: "\e158"; -} -.glyphicon-collapse-down:before { - content: "\e159"; -} -.glyphicon-collapse-up:before { - content: "\e160"; -} -.glyphicon-log-in:before { - content: "\e161"; -} -.glyphicon-flash:before { - content: "\e162"; -} -.glyphicon-log-out:before { - content: "\e163"; -} -.glyphicon-new-window:before { - content: "\e164"; -} -.glyphicon-record:before { - content: "\e165"; -} -.glyphicon-save:before { - content: "\e166"; -} -.glyphicon-open:before { - content: "\e167"; -} -.glyphicon-saved:before { - content: "\e168"; -} -.glyphicon-import:before { - content: "\e169"; -} -.glyphicon-export:before { - content: "\e170"; -} -.glyphicon-send:before { - content: "\e171"; -} -.glyphicon-floppy-disk:before { - content: "\e172"; -} -.glyphicon-floppy-saved:before { - content: "\e173"; -} -.glyphicon-floppy-remove:before { - content: "\e174"; -} -.glyphicon-floppy-save:before { - content: "\e175"; -} -.glyphicon-floppy-open:before { - content: "\e176"; -} -.glyphicon-credit-card:before { - content: "\e177"; -} -.glyphicon-transfer:before { - content: "\e178"; -} -.glyphicon-cutlery:before { - content: "\e179"; -} -.glyphicon-header:before { - content: "\e180"; -} -.glyphicon-compressed:before { - content: "\e181"; -} -.glyphicon-earphone:before { - content: "\e182"; -} -.glyphicon-phone-alt:before { - content: "\e183"; -} -.glyphicon-tower:before { - content: "\e184"; -} -.glyphicon-stats:before { - content: "\e185"; -} -.glyphicon-sd-video:before { - content: "\e186"; -} -.glyphicon-hd-video:before { - content: "\e187"; -} -.glyphicon-subtitles:before { - content: "\e188"; -} -.glyphicon-sound-stereo:before { - content: "\e189"; -} -.glyphicon-sound-dolby:before { - content: "\e190"; -} -.glyphicon-sound-5-1:before { - content: "\e191"; -} -.glyphicon-sound-6-1:before { - content: "\e192"; -} -.glyphicon-sound-7-1:before { - content: "\e193"; -} -.glyphicon-copyright-mark:before { - content: "\e194"; -} -.glyphicon-registration-mark:before { - content: "\e195"; -} -.glyphicon-cloud-download:before { - content: "\e197"; -} -.glyphicon-cloud-upload:before { - content: "\e198"; -} -.glyphicon-tree-conifer:before { - content: "\e199"; -} -.glyphicon-tree-deciduous:before { - content: "\e200"; -} -.glyphicon-cd:before { - content: "\e201"; -} -.glyphicon-save-file:before { - content: "\e202"; -} -.glyphicon-open-file:before { - content: "\e203"; -} -.glyphicon-level-up:before { - content: "\e204"; -} -.glyphicon-copy:before { - content: "\e205"; -} -.glyphicon-paste:before { - content: "\e206"; -} -.glyphicon-alert:before { - content: "\e209"; -} -.glyphicon-equalizer:before { - content: "\e210"; -} -.glyphicon-king:before { - content: "\e211"; -} -.glyphicon-queen:before { - content: "\e212"; -} -.glyphicon-pawn:before { - content: "\e213"; -} -.glyphicon-bishop:before { - content: "\e214"; -} -.glyphicon-knight:before { - content: "\e215"; -} -.glyphicon-baby-formula:before { - content: "\e216"; -} -.glyphicon-tent:before { - content: "\26fa"; -} -.glyphicon-blackboard:before { - content: "\e218"; -} -.glyphicon-bed:before { - content: "\e219"; -} -.glyphicon-apple:before { - content: "\f8ff"; -} -.glyphicon-erase:before { - content: "\e221"; -} -.glyphicon-hourglass:before { - content: "\231b"; -} -.glyphicon-lamp:before { - content: "\e223"; -} -.glyphicon-duplicate:before { - content: "\e224"; -} -.glyphicon-piggy-bank:before { - content: "\e225"; -} -.glyphicon-scissors:before { - content: "\e226"; -} -.glyphicon-bitcoin:before { - content: "\e227"; -} -.glyphicon-btc:before { - content: "\e227"; -} -.glyphicon-xbt:before { - content: "\e227"; -} -.glyphicon-yen:before { - content: "\00a5"; -} -.glyphicon-jpy:before { - content: "\00a5"; -} -.glyphicon-ruble:before { - content: "\20bd"; -} -.glyphicon-rub:before { - content: "\20bd"; -} -.glyphicon-scale:before { - content: "\e230"; -} -.glyphicon-ice-lolly:before { - content: "\e231"; -} -.glyphicon-ice-lolly-tasted:before { - content: "\e232"; -} -.glyphicon-education:before { - content: "\e233"; -} -.glyphicon-option-horizontal:before { - content: "\e234"; -} -.glyphicon-option-vertical:before { - content: "\e235"; -} -.glyphicon-menu-hamburger:before { - content: "\e236"; -} -.glyphicon-modal-window:before { - content: "\e237"; -} -.glyphicon-oil:before { - content: "\e238"; -} -.glyphicon-grain:before { - content: "\e239"; -} -.glyphicon-sunglasses:before { - content: "\e240"; -} -.glyphicon-text-size:before { - content: "\e241"; -} -.glyphicon-text-color:before { - content: "\e242"; -} -.glyphicon-text-background:before { - content: "\e243"; -} -.glyphicon-object-align-top:before { - content: "\e244"; -} -.glyphicon-object-align-bottom:before { - content: "\e245"; -} -.glyphicon-object-align-horizontal:before { - content: "\e246"; -} -.glyphicon-object-align-left:before { - content: "\e247"; -} -.glyphicon-object-align-vertical:before { - content: "\e248"; -} -.glyphicon-object-align-right:before { - content: "\e249"; -} -.glyphicon-triangle-right:before { - content: "\e250"; -} -.glyphicon-triangle-left:before { - content: "\e251"; -} -.glyphicon-triangle-bottom:before { - content: "\e252"; -} -.glyphicon-triangle-top:before { - content: "\e253"; -} -.glyphicon-console:before { - content: "\e254"; -} -.glyphicon-superscript:before { - content: "\e255"; -} -.glyphicon-subscript:before { - content: "\e256"; -} -.glyphicon-menu-left:before { - content: "\e257"; -} -.glyphicon-menu-right:before { - content: "\e258"; -} -.glyphicon-menu-down:before { - content: "\e259"; -} -.glyphicon-menu-up:before { - content: "\e260"; -} -* { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -*:before, -*:after { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -html { - font-size: 10px; - - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -} -body { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 1.42857143; - color: #333; - background-color: #fff; -} -input, -button, -select, -textarea { - font-family: inherit; - font-size: inherit; - line-height: inherit; -} -a { - color: #337ab7; - text-decoration: none; -} -a:hover, -a:focus { - color: #23527c; - text-decoration: underline; -} -a:focus { - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} -figure { - margin: 0; -} -img { - vertical-align: middle; -} -.img-responsive, -.thumbnail > img, -.thumbnail a > img, -.carousel-inner > .item > img, -.carousel-inner > .item > a > img { - display: block; - max-width: 100%; - height: auto; -} -.img-rounded { - border-radius: 6px; -} -.img-thumbnail { - display: inline-block; - max-width: 100%; - height: auto; - padding: 4px; - line-height: 1.42857143; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 4px; - -webkit-transition: all .2s ease-in-out; - -o-transition: all .2s ease-in-out; - transition: all .2s ease-in-out; -} -.img-circle { - border-radius: 50%; -} -hr { - margin-top: 20px; - margin-bottom: 20px; - border: 0; - border-top: 1px solid #eee; -} -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - border: 0; -} -.sr-only-focusable:active, -.sr-only-focusable:focus { - position: static; - width: auto; - height: auto; - margin: 0; - overflow: visible; - clip: auto; -} -[role="button"] { - cursor: pointer; -} -h1, -h2, -h3, -h4, -h5, -h6, -.h1, -.h2, -.h3, -.h4, -.h5, -.h6 { - font-family: inherit; - font-weight: 500; - line-height: 1.1; - color: inherit; -} -h1 small, -h2 small, -h3 small, -h4 small, -h5 small, -h6 small, -.h1 small, -.h2 small, -.h3 small, -.h4 small, -.h5 small, -.h6 small, -h1 .small, -h2 .small, -h3 .small, -h4 .small, -h5 .small, -h6 .small, -.h1 .small, -.h2 .small, -.h3 .small, -.h4 .small, -.h5 .small, -.h6 .small { - font-weight: normal; - line-height: 1; - color: #777; -} -h1, -.h1, -h2, -.h2, -h3, -.h3 { - margin-top: 20px; - margin-bottom: 10px; -} -h1 small, -.h1 small, -h2 small, -.h2 small, -h3 small, -.h3 small, -h1 .small, -.h1 .small, -h2 .small, -.h2 .small, -h3 .small, -.h3 .small { - font-size: 65%; -} -h4, -.h4, -h5, -.h5, -h6, -.h6 { - margin-top: 10px; - margin-bottom: 10px; -} -h4 small, -.h4 small, -h5 small, -.h5 small, -h6 small, -.h6 small, -h4 .small, -.h4 .small, -h5 .small, -.h5 .small, -h6 .small, -.h6 .small { - font-size: 75%; -} -h1, -.h1 { - font-size: 36px; -} -h2, -.h2 { - font-size: 30px; -} -h3, -.h3 { - font-size: 24px; -} -h4, -.h4 { - font-size: 18px; -} -h5, -.h5 { - font-size: 14px; -} -h6, -.h6 { - font-size: 12px; -} -p { - margin: 0 0 10px; -} -.lead { - margin-bottom: 20px; - font-size: 16px; - font-weight: 300; - line-height: 1.4; -} -@media (min-width: 768px) { - .lead { - font-size: 21px; - } -} -small, -.small { - font-size: 85%; -} -mark, -.mark { - padding: .2em; - background-color: #fcf8e3; -} -.text-left { - text-align: left; -} -.text-right { - text-align: right; -} -.text-center { - text-align: center; -} -.text-justify { - text-align: justify; -} -.text-nowrap { - white-space: nowrap; -} -.text-lowercase { - text-transform: lowercase; -} -.text-uppercase { - text-transform: uppercase; -} -.text-capitalize { - text-transform: capitalize; -} -.text-muted { - color: #777; -} -.text-primary { - color: #337ab7; -} -a.text-primary:hover, -a.text-primary:focus { - color: #286090; -} -.text-success { - color: #3c763d; -} -a.text-success:hover, -a.text-success:focus { - color: #2b542c; -} -.text-info { - color: #31708f; -} -a.text-info:hover, -a.text-info:focus { - color: #245269; -} -.text-warning { - color: #8a6d3b; -} -a.text-warning:hover, -a.text-warning:focus { - color: #66512c; -} -.text-danger { - color: #a94442; -} -a.text-danger:hover, -a.text-danger:focus { - color: #843534; -} -.bg-primary { - color: #fff; - background-color: #337ab7; -} -a.bg-primary:hover, -a.bg-primary:focus { - background-color: #286090; -} -.bg-success { - background-color: #dff0d8; -} -a.bg-success:hover, -a.bg-success:focus { - background-color: #c1e2b3; -} -.bg-info { - background-color: #d9edf7; -} -a.bg-info:hover, -a.bg-info:focus { - background-color: #afd9ee; -} -.bg-warning { - background-color: #fcf8e3; -} -a.bg-warning:hover, -a.bg-warning:focus { - background-color: #f7ecb5; -} -.bg-danger { - background-color: #f2dede; -} -a.bg-danger:hover, -a.bg-danger:focus { - background-color: #e4b9b9; -} -.page-header { - padding-bottom: 9px; - margin: 40px 0 20px; - border-bottom: 1px solid #eee; -} -ul, -ol { - margin-top: 0; - margin-bottom: 10px; -} -ul ul, -ol ul, -ul ol, -ol ol { - margin-bottom: 0; -} -.list-unstyled { - padding-left: 0; - list-style: none; -} -.list-inline { - padding-left: 0; - margin-left: -5px; - list-style: none; -} -.list-inline > li { - display: inline-block; - padding-right: 5px; - padding-left: 5px; -} -dl { - margin-top: 0; - margin-bottom: 20px; -} -dt, -dd { - line-height: 1.42857143; -} -dt { - font-weight: bold; -} -dd { - margin-left: 0; -} -@media (min-width: 768px) { - .dl-horizontal dt { - float: left; - width: 160px; - overflow: hidden; - clear: left; - text-align: right; - text-overflow: ellipsis; - white-space: nowrap; - } - .dl-horizontal dd { - margin-left: 180px; - } -} -abbr[title], -abbr[data-original-title] { - cursor: help; - border-bottom: 1px dotted #777; -} -.initialism { - font-size: 90%; - text-transform: uppercase; -} -blockquote { - padding: 10px 20px; - margin: 0 0 20px; - font-size: 17.5px; - border-left: 5px solid #eee; -} -blockquote p:last-child, -blockquote ul:last-child, -blockquote ol:last-child { - margin-bottom: 0; -} -blockquote footer, -blockquote small, -blockquote .small { - display: block; - font-size: 80%; - line-height: 1.42857143; - color: #777; -} -blockquote footer:before, -blockquote small:before, -blockquote .small:before { - content: '\2014 \00A0'; -} -.blockquote-reverse, -blockquote.pull-right { - padding-right: 15px; - padding-left: 0; - text-align: right; - border-right: 5px solid #eee; - border-left: 0; -} -.blockquote-reverse footer:before, -blockquote.pull-right footer:before, -.blockquote-reverse small:before, -blockquote.pull-right small:before, -.blockquote-reverse .small:before, -blockquote.pull-right .small:before { - content: ''; -} -.blockquote-reverse footer:after, -blockquote.pull-right footer:after, -.blockquote-reverse small:after, -blockquote.pull-right small:after, -.blockquote-reverse .small:after, -blockquote.pull-right .small:after { - content: '\00A0 \2014'; -} -address { - margin-bottom: 20px; - font-style: normal; - line-height: 1.42857143; -} -code, -kbd, -pre, -samp { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; -} -code { - padding: 2px 4px; - font-size: 90%; - color: #c7254e; - background-color: #f9f2f4; - border-radius: 4px; -} -kbd { - padding: 2px 4px; - font-size: 90%; - color: #fff; - background-color: #333; - border-radius: 3px; - -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); -} -kbd kbd { - padding: 0; - font-size: 100%; - font-weight: bold; - -webkit-box-shadow: none; - box-shadow: none; -} -pre { - display: block; - padding: 9.5px; - margin: 0 0 10px; - font-size: 13px; - line-height: 1.42857143; - color: #333; - word-break: break-all; - word-wrap: break-word; - background-color: #f5f5f5; - border: 1px solid #ccc; - border-radius: 4px; -} -pre code { - padding: 0; - font-size: inherit; - color: inherit; - white-space: pre-wrap; - background-color: transparent; - border-radius: 0; -} -.pre-scrollable { - max-height: 340px; - overflow-y: scroll; -} -.container { - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; -} -@media (min-width: 768px) { - .container { - width: 750px; - } -} -@media (min-width: 992px) { - .container { - width: 970px; - } -} -@media (min-width: 1200px) { - .container { - width: 1170px; - } -} -.container-fluid { - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; -} -.row { - margin-right: -15px; - margin-left: -15px; -} -.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { - position: relative; - min-height: 1px; - padding-right: 15px; - padding-left: 15px; -} -.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { - float: left; -} -.col-xs-12 { - width: 100%; -} -.col-xs-11 { - width: 91.66666667%; -} -.col-xs-10 { - width: 83.33333333%; -} -.col-xs-9 { - width: 75%; -} -.col-xs-8 { - width: 66.66666667%; -} -.col-xs-7 { - width: 58.33333333%; -} -.col-xs-6 { - width: 50%; -} -.col-xs-5 { - width: 41.66666667%; -} -.col-xs-4 { - width: 33.33333333%; -} -.col-xs-3 { - width: 25%; -} -.col-xs-2 { - width: 16.66666667%; -} -.col-xs-1 { - width: 8.33333333%; -} -.col-xs-pull-12 { - right: 100%; -} -.col-xs-pull-11 { - right: 91.66666667%; -} -.col-xs-pull-10 { - right: 83.33333333%; -} -.col-xs-pull-9 { - right: 75%; -} -.col-xs-pull-8 { - right: 66.66666667%; -} -.col-xs-pull-7 { - right: 58.33333333%; -} -.col-xs-pull-6 { - right: 50%; -} -.col-xs-pull-5 { - right: 41.66666667%; -} -.col-xs-pull-4 { - right: 33.33333333%; -} -.col-xs-pull-3 { - right: 25%; -} -.col-xs-pull-2 { - right: 16.66666667%; -} -.col-xs-pull-1 { - right: 8.33333333%; -} -.col-xs-pull-0 { - right: auto; -} -.col-xs-push-12 { - left: 100%; -} -.col-xs-push-11 { - left: 91.66666667%; -} -.col-xs-push-10 { - left: 83.33333333%; -} -.col-xs-push-9 { - left: 75%; -} -.col-xs-push-8 { - left: 66.66666667%; -} -.col-xs-push-7 { - left: 58.33333333%; -} -.col-xs-push-6 { - left: 50%; -} -.col-xs-push-5 { - left: 41.66666667%; -} -.col-xs-push-4 { - left: 33.33333333%; -} -.col-xs-push-3 { - left: 25%; -} -.col-xs-push-2 { - left: 16.66666667%; -} -.col-xs-push-1 { - left: 8.33333333%; -} -.col-xs-push-0 { - left: auto; -} -.col-xs-offset-12 { - margin-left: 100%; -} -.col-xs-offset-11 { - margin-left: 91.66666667%; -} -.col-xs-offset-10 { - margin-left: 83.33333333%; -} -.col-xs-offset-9 { - margin-left: 75%; -} -.col-xs-offset-8 { - margin-left: 66.66666667%; -} -.col-xs-offset-7 { - margin-left: 58.33333333%; -} -.col-xs-offset-6 { - margin-left: 50%; -} -.col-xs-offset-5 { - margin-left: 41.66666667%; -} -.col-xs-offset-4 { - margin-left: 33.33333333%; -} -.col-xs-offset-3 { - margin-left: 25%; -} -.col-xs-offset-2 { - margin-left: 16.66666667%; -} -.col-xs-offset-1 { - margin-left: 8.33333333%; -} -.col-xs-offset-0 { - margin-left: 0; -} -@media (min-width: 768px) { - .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { - float: left; - } - .col-sm-12 { - width: 100%; - } - .col-sm-11 { - width: 91.66666667%; - } - .col-sm-10 { - width: 83.33333333%; - } - .col-sm-9 { - width: 75%; - } - .col-sm-8 { - width: 66.66666667%; - } - .col-sm-7 { - width: 58.33333333%; - } - .col-sm-6 { - width: 50%; - } - .col-sm-5 { - width: 41.66666667%; - } - .col-sm-4 { - width: 33.33333333%; - } - .col-sm-3 { - width: 25%; - } - .col-sm-2 { - width: 16.66666667%; - } - .col-sm-1 { - width: 8.33333333%; - } - .col-sm-pull-12 { - right: 100%; - } - .col-sm-pull-11 { - right: 91.66666667%; - } - .col-sm-pull-10 { - right: 83.33333333%; - } - .col-sm-pull-9 { - right: 75%; - } - .col-sm-pull-8 { - right: 66.66666667%; - } - .col-sm-pull-7 { - right: 58.33333333%; - } - .col-sm-pull-6 { - right: 50%; - } - .col-sm-pull-5 { - right: 41.66666667%; - } - .col-sm-pull-4 { - right: 33.33333333%; - } - .col-sm-pull-3 { - right: 25%; - } - .col-sm-pull-2 { - right: 16.66666667%; - } - .col-sm-pull-1 { - right: 8.33333333%; - } - .col-sm-pull-0 { - right: auto; - } - .col-sm-push-12 { - left: 100%; - } - .col-sm-push-11 { - left: 91.66666667%; - } - .col-sm-push-10 { - left: 83.33333333%; - } - .col-sm-push-9 { - left: 75%; - } - .col-sm-push-8 { - left: 66.66666667%; - } - .col-sm-push-7 { - left: 58.33333333%; - } - .col-sm-push-6 { - left: 50%; - } - .col-sm-push-5 { - left: 41.66666667%; - } - .col-sm-push-4 { - left: 33.33333333%; - } - .col-sm-push-3 { - left: 25%; - } - .col-sm-push-2 { - left: 16.66666667%; - } - .col-sm-push-1 { - left: 8.33333333%; - } - .col-sm-push-0 { - left: auto; - } - .col-sm-offset-12 { - margin-left: 100%; - } - .col-sm-offset-11 { - margin-left: 91.66666667%; - } - .col-sm-offset-10 { - margin-left: 83.33333333%; - } - .col-sm-offset-9 { - margin-left: 75%; - } - .col-sm-offset-8 { - margin-left: 66.66666667%; - } - .col-sm-offset-7 { - margin-left: 58.33333333%; - } - .col-sm-offset-6 { - margin-left: 50%; - } - .col-sm-offset-5 { - margin-left: 41.66666667%; - } - .col-sm-offset-4 { - margin-left: 33.33333333%; - } - .col-sm-offset-3 { - margin-left: 25%; - } - .col-sm-offset-2 { - margin-left: 16.66666667%; - } - .col-sm-offset-1 { - margin-left: 8.33333333%; - } - .col-sm-offset-0 { - margin-left: 0; - } -} -@media (min-width: 992px) { - .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { - float: left; - } - .col-md-12 { - width: 100%; - } - .col-md-11 { - width: 91.66666667%; - } - .col-md-10 { - width: 83.33333333%; - } - .col-md-9 { - width: 75%; - } - .col-md-8 { - width: 66.66666667%; - } - .col-md-7 { - width: 58.33333333%; - } - .col-md-6 { - width: 50%; - } - .col-md-5 { - width: 41.66666667%; - } - .col-md-4 { - width: 33.33333333%; - } - .col-md-3 { - width: 25%; - } - .col-md-2 { - width: 16.66666667%; - } - .col-md-1 { - width: 8.33333333%; - } - .col-md-pull-12 { - right: 100%; - } - .col-md-pull-11 { - right: 91.66666667%; - } - .col-md-pull-10 { - right: 83.33333333%; - } - .col-md-pull-9 { - right: 75%; - } - .col-md-pull-8 { - right: 66.66666667%; - } - .col-md-pull-7 { - right: 58.33333333%; - } - .col-md-pull-6 { - right: 50%; - } - .col-md-pull-5 { - right: 41.66666667%; - } - .col-md-pull-4 { - right: 33.33333333%; - } - .col-md-pull-3 { - right: 25%; - } - .col-md-pull-2 { - right: 16.66666667%; - } - .col-md-pull-1 { - right: 8.33333333%; - } - .col-md-pull-0 { - right: auto; - } - .col-md-push-12 { - left: 100%; - } - .col-md-push-11 { - left: 91.66666667%; - } - .col-md-push-10 { - left: 83.33333333%; - } - .col-md-push-9 { - left: 75%; - } - .col-md-push-8 { - left: 66.66666667%; - } - .col-md-push-7 { - left: 58.33333333%; - } - .col-md-push-6 { - left: 50%; - } - .col-md-push-5 { - left: 41.66666667%; - } - .col-md-push-4 { - left: 33.33333333%; - } - .col-md-push-3 { - left: 25%; - } - .col-md-push-2 { - left: 16.66666667%; - } - .col-md-push-1 { - left: 8.33333333%; - } - .col-md-push-0 { - left: auto; - } - .col-md-offset-12 { - margin-left: 100%; - } - .col-md-offset-11 { - margin-left: 91.66666667%; - } - .col-md-offset-10 { - margin-left: 83.33333333%; - } - .col-md-offset-9 { - margin-left: 75%; - } - .col-md-offset-8 { - margin-left: 66.66666667%; - } - .col-md-offset-7 { - margin-left: 58.33333333%; - } - .col-md-offset-6 { - margin-left: 50%; - } - .col-md-offset-5 { - margin-left: 41.66666667%; - } - .col-md-offset-4 { - margin-left: 33.33333333%; - } - .col-md-offset-3 { - margin-left: 25%; - } - .col-md-offset-2 { - margin-left: 16.66666667%; - } - .col-md-offset-1 { - margin-left: 8.33333333%; - } - .col-md-offset-0 { - margin-left: 0; - } -} -@media (min-width: 1200px) { - .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { - float: left; - } - .col-lg-12 { - width: 100%; - } - .col-lg-11 { - width: 91.66666667%; - } - .col-lg-10 { - width: 83.33333333%; - } - .col-lg-9 { - width: 75%; - } - .col-lg-8 { - width: 66.66666667%; - } - .col-lg-7 { - width: 58.33333333%; - } - .col-lg-6 { - width: 50%; - } - .col-lg-5 { - width: 41.66666667%; - } - .col-lg-4 { - width: 33.33333333%; - } - .col-lg-3 { - width: 25%; - } - .col-lg-2 { - width: 16.66666667%; - } - .col-lg-1 { - width: 8.33333333%; - } - .col-lg-pull-12 { - right: 100%; - } - .col-lg-pull-11 { - right: 91.66666667%; - } - .col-lg-pull-10 { - right: 83.33333333%; - } - .col-lg-pull-9 { - right: 75%; - } - .col-lg-pull-8 { - right: 66.66666667%; - } - .col-lg-pull-7 { - right: 58.33333333%; - } - .col-lg-pull-6 { - right: 50%; - } - .col-lg-pull-5 { - right: 41.66666667%; - } - .col-lg-pull-4 { - right: 33.33333333%; - } - .col-lg-pull-3 { - right: 25%; - } - .col-lg-pull-2 { - right: 16.66666667%; - } - .col-lg-pull-1 { - right: 8.33333333%; - } - .col-lg-pull-0 { - right: auto; - } - .col-lg-push-12 { - left: 100%; - } - .col-lg-push-11 { - left: 91.66666667%; - } - .col-lg-push-10 { - left: 83.33333333%; - } - .col-lg-push-9 { - left: 75%; - } - .col-lg-push-8 { - left: 66.66666667%; - } - .col-lg-push-7 { - left: 58.33333333%; - } - .col-lg-push-6 { - left: 50%; - } - .col-lg-push-5 { - left: 41.66666667%; - } - .col-lg-push-4 { - left: 33.33333333%; - } - .col-lg-push-3 { - left: 25%; - } - .col-lg-push-2 { - left: 16.66666667%; - } - .col-lg-push-1 { - left: 8.33333333%; - } - .col-lg-push-0 { - left: auto; - } - .col-lg-offset-12 { - margin-left: 100%; - } - .col-lg-offset-11 { - margin-left: 91.66666667%; - } - .col-lg-offset-10 { - margin-left: 83.33333333%; - } - .col-lg-offset-9 { - margin-left: 75%; - } - .col-lg-offset-8 { - margin-left: 66.66666667%; - } - .col-lg-offset-7 { - margin-left: 58.33333333%; - } - .col-lg-offset-6 { - margin-left: 50%; - } - .col-lg-offset-5 { - margin-left: 41.66666667%; - } - .col-lg-offset-4 { - margin-left: 33.33333333%; - } - .col-lg-offset-3 { - margin-left: 25%; - } - .col-lg-offset-2 { - margin-left: 16.66666667%; - } - .col-lg-offset-1 { - margin-left: 8.33333333%; - } - .col-lg-offset-0 { - margin-left: 0; - } -} -table { - background-color: transparent; -} -caption { - padding-top: 8px; - padding-bottom: 8px; - color: #777; - text-align: left; -} -th { - text-align: left; -} -.table { - width: 100%; - max-width: 100%; - margin-bottom: 20px; -} -.table > thead > tr > th, -.table > tbody > tr > th, -.table > tfoot > tr > th, -.table > thead > tr > td, -.table > tbody > tr > td, -.table > tfoot > tr > td { - padding: 8px; - line-height: 1.42857143; - vertical-align: top; - border-top: 1px solid #ddd; -} -.table > thead > tr > th { - vertical-align: bottom; - border-bottom: 2px solid #ddd; -} -.table > caption + thead > tr:first-child > th, -.table > colgroup + thead > tr:first-child > th, -.table > thead:first-child > tr:first-child > th, -.table > caption + thead > tr:first-child > td, -.table > colgroup + thead > tr:first-child > td, -.table > thead:first-child > tr:first-child > td { - border-top: 0; -} -.table > tbody + tbody { - border-top: 2px solid #ddd; -} -.table .table { - background-color: #fff; -} -.table-condensed > thead > tr > th, -.table-condensed > tbody > tr > th, -.table-condensed > tfoot > tr > th, -.table-condensed > thead > tr > td, -.table-condensed > tbody > tr > td, -.table-condensed > tfoot > tr > td { - padding: 5px; -} -.table-bordered { - border: 1px solid #ddd; -} -.table-bordered > thead > tr > th, -.table-bordered > tbody > tr > th, -.table-bordered > tfoot > tr > th, -.table-bordered > thead > tr > td, -.table-bordered > tbody > tr > td, -.table-bordered > tfoot > tr > td { - border: 1px solid #ddd; -} -.table-bordered > thead > tr > th, -.table-bordered > thead > tr > td { - border-bottom-width: 2px; -} -.table-striped > tbody > tr:nth-of-type(odd) { - background-color: #f9f9f9; -} -.table-hover > tbody > tr:hover { - background-color: #f5f5f5; -} -table col[class*="col-"] { - position: static; - display: table-column; - float: none; -} -table td[class*="col-"], -table th[class*="col-"] { - position: static; - display: table-cell; - float: none; -} -.table > thead > tr > td.active, -.table > tbody > tr > td.active, -.table > tfoot > tr > td.active, -.table > thead > tr > th.active, -.table > tbody > tr > th.active, -.table > tfoot > tr > th.active, -.table > thead > tr.active > td, -.table > tbody > tr.active > td, -.table > tfoot > tr.active > td, -.table > thead > tr.active > th, -.table > tbody > tr.active > th, -.table > tfoot > tr.active > th { - background-color: #f5f5f5; -} -.table-hover > tbody > tr > td.active:hover, -.table-hover > tbody > tr > th.active:hover, -.table-hover > tbody > tr.active:hover > td, -.table-hover > tbody > tr:hover > .active, -.table-hover > tbody > tr.active:hover > th { - background-color: #e8e8e8; -} -.table > thead > tr > td.success, -.table > tbody > tr > td.success, -.table > tfoot > tr > td.success, -.table > thead > tr > th.success, -.table > tbody > tr > th.success, -.table > tfoot > tr > th.success, -.table > thead > tr.success > td, -.table > tbody > tr.success > td, -.table > tfoot > tr.success > td, -.table > thead > tr.success > th, -.table > tbody > tr.success > th, -.table > tfoot > tr.success > th { - background-color: #dff0d8; -} -.table-hover > tbody > tr > td.success:hover, -.table-hover > tbody > tr > th.success:hover, -.table-hover > tbody > tr.success:hover > td, -.table-hover > tbody > tr:hover > .success, -.table-hover > tbody > tr.success:hover > th { - background-color: #d0e9c6; -} -.table > thead > tr > td.info, -.table > tbody > tr > td.info, -.table > tfoot > tr > td.info, -.table > thead > tr > th.info, -.table > tbody > tr > th.info, -.table > tfoot > tr > th.info, -.table > thead > tr.info > td, -.table > tbody > tr.info > td, -.table > tfoot > tr.info > td, -.table > thead > tr.info > th, -.table > tbody > tr.info > th, -.table > tfoot > tr.info > th { - background-color: #d9edf7; -} -.table-hover > tbody > tr > td.info:hover, -.table-hover > tbody > tr > th.info:hover, -.table-hover > tbody > tr.info:hover > td, -.table-hover > tbody > tr:hover > .info, -.table-hover > tbody > tr.info:hover > th { - background-color: #c4e3f3; -} -.table > thead > tr > td.warning, -.table > tbody > tr > td.warning, -.table > tfoot > tr > td.warning, -.table > thead > tr > th.warning, -.table > tbody > tr > th.warning, -.table > tfoot > tr > th.warning, -.table > thead > tr.warning > td, -.table > tbody > tr.warning > td, -.table > tfoot > tr.warning > td, -.table > thead > tr.warning > th, -.table > tbody > tr.warning > th, -.table > tfoot > tr.warning > th { - background-color: #fcf8e3; -} -.table-hover > tbody > tr > td.warning:hover, -.table-hover > tbody > tr > th.warning:hover, -.table-hover > tbody > tr.warning:hover > td, -.table-hover > tbody > tr:hover > .warning, -.table-hover > tbody > tr.warning:hover > th { - background-color: #faf2cc; -} -.table > thead > tr > td.danger, -.table > tbody > tr > td.danger, -.table > tfoot > tr > td.danger, -.table > thead > tr > th.danger, -.table > tbody > tr > th.danger, -.table > tfoot > tr > th.danger, -.table > thead > tr.danger > td, -.table > tbody > tr.danger > td, -.table > tfoot > tr.danger > td, -.table > thead > tr.danger > th, -.table > tbody > tr.danger > th, -.table > tfoot > tr.danger > th { - background-color: #f2dede; -} -.table-hover > tbody > tr > td.danger:hover, -.table-hover > tbody > tr > th.danger:hover, -.table-hover > tbody > tr.danger:hover > td, -.table-hover > tbody > tr:hover > .danger, -.table-hover > tbody > tr.danger:hover > th { - background-color: #ebcccc; -} -.table-responsive { - min-height: .01%; - overflow-x: auto; -} -@media screen and (max-width: 767px) { - .table-responsive { - width: 100%; - margin-bottom: 15px; - overflow-y: hidden; - -ms-overflow-style: -ms-autohiding-scrollbar; - border: 1px solid #ddd; - } - .table-responsive > .table { - margin-bottom: 0; - } - .table-responsive > .table > thead > tr > th, - .table-responsive > .table > tbody > tr > th, - .table-responsive > .table > tfoot > tr > th, - .table-responsive > .table > thead > tr > td, - .table-responsive > .table > tbody > tr > td, - .table-responsive > .table > tfoot > tr > td { - white-space: nowrap; - } - .table-responsive > .table-bordered { - border: 0; - } - .table-responsive > .table-bordered > thead > tr > th:first-child, - .table-responsive > .table-bordered > tbody > tr > th:first-child, - .table-responsive > .table-bordered > tfoot > tr > th:first-child, - .table-responsive > .table-bordered > thead > tr > td:first-child, - .table-responsive > .table-bordered > tbody > tr > td:first-child, - .table-responsive > .table-bordered > tfoot > tr > td:first-child { - border-left: 0; - } - .table-responsive > .table-bordered > thead > tr > th:last-child, - .table-responsive > .table-bordered > tbody > tr > th:last-child, - .table-responsive > .table-bordered > tfoot > tr > th:last-child, - .table-responsive > .table-bordered > thead > tr > td:last-child, - .table-responsive > .table-bordered > tbody > tr > td:last-child, - .table-responsive > .table-bordered > tfoot > tr > td:last-child { - border-right: 0; - } - .table-responsive > .table-bordered > tbody > tr:last-child > th, - .table-responsive > .table-bordered > tfoot > tr:last-child > th, - .table-responsive > .table-bordered > tbody > tr:last-child > td, - .table-responsive > .table-bordered > tfoot > tr:last-child > td { - border-bottom: 0; - } -} -fieldset { - min-width: 0; - padding: 0; - margin: 0; - border: 0; -} -legend { - display: block; - width: 100%; - padding: 0; - margin-bottom: 20px; - font-size: 21px; - line-height: inherit; - color: #333; - border: 0; - border-bottom: 1px solid #e5e5e5; -} -label { - display: inline-block; - max-width: 100%; - margin-bottom: 5px; - font-weight: bold; -} -input[type="search"] { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -input[type="radio"], -input[type="checkbox"] { - margin: 4px 0 0; - margin-top: 1px \9; - line-height: normal; -} -input[type="file"] { - display: block; -} -input[type="range"] { - display: block; - width: 100%; -} -select[multiple], -select[size] { - height: auto; -} -input[type="file"]:focus, -input[type="radio"]:focus, -input[type="checkbox"]:focus { - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} -output { - display: block; - padding-top: 7px; - font-size: 14px; - line-height: 1.42857143; - color: #555; -} -.form-control { - display: block; - width: 100%; - height: 34px; - padding: 6px 12px; - font-size: 14px; - line-height: 1.42857143; - color: #555; - background-color: #fff; - background-image: none; - border: 1px solid #ccc; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; - -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; - transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; -} -.form-control:focus { - border-color: #66afe9; - outline: 0; - -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); - box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); -} -.form-control::-moz-placeholder { - color: #999; - opacity: 1; -} -.form-control:-ms-input-placeholder { - color: #999; -} -.form-control::-webkit-input-placeholder { - color: #999; -} -.form-control::-ms-expand { - background-color: transparent; - border: 0; -} -.form-control[disabled], -.form-control[readonly], -fieldset[disabled] .form-control { - background-color: #eee; - opacity: 1; -} -.form-control[disabled], -fieldset[disabled] .form-control { - cursor: not-allowed; -} -textarea.form-control { - height: auto; -} -input[type="search"] { - -webkit-appearance: none; -} -@media screen and (-webkit-min-device-pixel-ratio: 0) { - input[type="date"].form-control, - input[type="time"].form-control, - input[type="datetime-local"].form-control, - input[type="month"].form-control { - line-height: 34px; - } - input[type="date"].input-sm, - input[type="time"].input-sm, - input[type="datetime-local"].input-sm, - input[type="month"].input-sm, - .input-group-sm input[type="date"], - .input-group-sm input[type="time"], - .input-group-sm input[type="datetime-local"], - .input-group-sm input[type="month"] { - line-height: 30px; - } - input[type="date"].input-lg, - input[type="time"].input-lg, - input[type="datetime-local"].input-lg, - input[type="month"].input-lg, - .input-group-lg input[type="date"], - .input-group-lg input[type="time"], - .input-group-lg input[type="datetime-local"], - .input-group-lg input[type="month"] { - line-height: 46px; - } -} -.form-group { - margin-bottom: 15px; -} -.radio, -.checkbox { - position: relative; - display: block; - margin-top: 10px; - margin-bottom: 10px; -} -.radio label, -.checkbox label { - min-height: 20px; - padding-left: 20px; - margin-bottom: 0; - font-weight: normal; - cursor: pointer; -} -.radio input[type="radio"], -.radio-inline input[type="radio"], -.checkbox input[type="checkbox"], -.checkbox-inline input[type="checkbox"] { - position: absolute; - margin-top: 4px \9; - margin-left: -20px; -} -.radio + .radio, -.checkbox + .checkbox { - margin-top: -5px; -} -.radio-inline, -.checkbox-inline { - position: relative; - display: inline-block; - padding-left: 20px; - margin-bottom: 0; - font-weight: normal; - vertical-align: middle; - cursor: pointer; -} -.radio-inline + .radio-inline, -.checkbox-inline + .checkbox-inline { - margin-top: 0; - margin-left: 10px; -} -input[type="radio"][disabled], -input[type="checkbox"][disabled], -input[type="radio"].disabled, -input[type="checkbox"].disabled, -fieldset[disabled] input[type="radio"], -fieldset[disabled] input[type="checkbox"] { - cursor: not-allowed; -} -.radio-inline.disabled, -.checkbox-inline.disabled, -fieldset[disabled] .radio-inline, -fieldset[disabled] .checkbox-inline { - cursor: not-allowed; -} -.radio.disabled label, -.checkbox.disabled label, -fieldset[disabled] .radio label, -fieldset[disabled] .checkbox label { - cursor: not-allowed; -} -.form-control-static { - min-height: 34px; - padding-top: 7px; - padding-bottom: 7px; - margin-bottom: 0; -} -.form-control-static.input-lg, -.form-control-static.input-sm { - padding-right: 0; - padding-left: 0; -} -.input-sm { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -select.input-sm { - height: 30px; - line-height: 30px; -} -textarea.input-sm, -select[multiple].input-sm { - height: auto; -} -.form-group-sm .form-control { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -.form-group-sm select.form-control { - height: 30px; - line-height: 30px; -} -.form-group-sm textarea.form-control, -.form-group-sm select[multiple].form-control { - height: auto; -} -.form-group-sm .form-control-static { - height: 30px; - min-height: 32px; - padding: 6px 10px; - font-size: 12px; - line-height: 1.5; -} -.input-lg { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px; -} -select.input-lg { - height: 46px; - line-height: 46px; -} -textarea.input-lg, -select[multiple].input-lg { - height: auto; -} -.form-group-lg .form-control { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px; -} -.form-group-lg select.form-control { - height: 46px; - line-height: 46px; -} -.form-group-lg textarea.form-control, -.form-group-lg select[multiple].form-control { - height: auto; -} -.form-group-lg .form-control-static { - height: 46px; - min-height: 38px; - padding: 11px 16px; - font-size: 18px; - line-height: 1.3333333; -} -.has-feedback { - position: relative; -} -.has-feedback .form-control { - padding-right: 42.5px; -} -.form-control-feedback { - position: absolute; - top: 0; - right: 0; - z-index: 2; - display: block; - width: 34px; - height: 34px; - line-height: 34px; - text-align: center; - pointer-events: none; -} -.input-lg + .form-control-feedback, -.input-group-lg + .form-control-feedback, -.form-group-lg .form-control + .form-control-feedback { - width: 46px; - height: 46px; - line-height: 46px; -} -.input-sm + .form-control-feedback, -.input-group-sm + .form-control-feedback, -.form-group-sm .form-control + .form-control-feedback { - width: 30px; - height: 30px; - line-height: 30px; -} -.has-success .help-block, -.has-success .control-label, -.has-success .radio, -.has-success .checkbox, -.has-success .radio-inline, -.has-success .checkbox-inline, -.has-success.radio label, -.has-success.checkbox label, -.has-success.radio-inline label, -.has-success.checkbox-inline label { - color: #3c763d; -} -.has-success .form-control { - border-color: #3c763d; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); -} -.has-success .form-control:focus { - border-color: #2b542c; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; -} -.has-success .input-group-addon { - color: #3c763d; - background-color: #dff0d8; - border-color: #3c763d; -} -.has-success .form-control-feedback { - color: #3c763d; -} -.has-warning .help-block, -.has-warning .control-label, -.has-warning .radio, -.has-warning .checkbox, -.has-warning .radio-inline, -.has-warning .checkbox-inline, -.has-warning.radio label, -.has-warning.checkbox label, -.has-warning.radio-inline label, -.has-warning.checkbox-inline label { - color: #8a6d3b; -} -.has-warning .form-control { - border-color: #8a6d3b; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); -} -.has-warning .form-control:focus { - border-color: #66512c; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; -} -.has-warning .input-group-addon { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #8a6d3b; -} -.has-warning .form-control-feedback { - color: #8a6d3b; -} -.has-error .help-block, -.has-error .control-label, -.has-error .radio, -.has-error .checkbox, -.has-error .radio-inline, -.has-error .checkbox-inline, -.has-error.radio label, -.has-error.checkbox label, -.has-error.radio-inline label, -.has-error.checkbox-inline label { - color: #a94442; -} -.has-error .form-control { - border-color: #a94442; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); -} -.has-error .form-control:focus { - border-color: #843534; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; -} -.has-error .input-group-addon { - color: #a94442; - background-color: #f2dede; - border-color: #a94442; -} -.has-error .form-control-feedback { - color: #a94442; -} -.has-feedback label ~ .form-control-feedback { - top: 25px; -} -.has-feedback label.sr-only ~ .form-control-feedback { - top: 0; -} -.help-block { - display: block; - margin-top: 5px; - margin-bottom: 10px; - color: #737373; -} -@media (min-width: 768px) { - .form-inline .form-group { - display: inline-block; - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .form-control { - display: inline-block; - width: auto; - vertical-align: middle; - } - .form-inline .form-control-static { - display: inline-block; - } - .form-inline .input-group { - display: inline-table; - vertical-align: middle; - } - .form-inline .input-group .input-group-addon, - .form-inline .input-group .input-group-btn, - .form-inline .input-group .form-control { - width: auto; - } - .form-inline .input-group > .form-control { - width: 100%; - } - .form-inline .control-label { - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .radio, - .form-inline .checkbox { - display: inline-block; - margin-top: 0; - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .radio label, - .form-inline .checkbox label { - padding-left: 0; - } - .form-inline .radio input[type="radio"], - .form-inline .checkbox input[type="checkbox"] { - position: relative; - margin-left: 0; - } - .form-inline .has-feedback .form-control-feedback { - top: 0; - } -} -.form-horizontal .radio, -.form-horizontal .checkbox, -.form-horizontal .radio-inline, -.form-horizontal .checkbox-inline { - padding-top: 7px; - margin-top: 0; - margin-bottom: 0; -} -.form-horizontal .radio, -.form-horizontal .checkbox { - min-height: 27px; -} -.form-horizontal .form-group { - margin-right: -15px; - margin-left: -15px; -} -@media (min-width: 768px) { - .form-horizontal .control-label { - padding-top: 7px; - margin-bottom: 0; - text-align: right; - } -} -.form-horizontal .has-feedback .form-control-feedback { - right: 15px; -} -@media (min-width: 768px) { - .form-horizontal .form-group-lg .control-label { - padding-top: 11px; - font-size: 18px; - } -} -@media (min-width: 768px) { - .form-horizontal .form-group-sm .control-label { - padding-top: 6px; - font-size: 12px; - } -} -.btn { - display: inline-block; - padding: 6px 12px; - margin-bottom: 0; - font-size: 14px; - font-weight: normal; - line-height: 1.42857143; - text-align: center; - white-space: nowrap; - vertical-align: middle; - -ms-touch-action: manipulation; - touch-action: manipulation; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-image: none; - border: 1px solid transparent; - border-radius: 4px; -} -.btn:focus, -.btn:active:focus, -.btn.active:focus, -.btn.focus, -.btn:active.focus, -.btn.active.focus { - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} -.btn:hover, -.btn:focus, -.btn.focus { - color: #333; - text-decoration: none; -} -.btn:active, -.btn.active { - background-image: none; - outline: 0; - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); -} -.btn.disabled, -.btn[disabled], -fieldset[disabled] .btn { - cursor: not-allowed; - filter: alpha(opacity=65); - -webkit-box-shadow: none; - box-shadow: none; - opacity: .65; -} -a.btn.disabled, -fieldset[disabled] a.btn { - pointer-events: none; -} -.btn-default { - color: #333; - background-color: #fff; - border-color: #ccc; -} -.btn-default:focus, -.btn-default.focus { - color: #333; - background-color: #e6e6e6; - border-color: #8c8c8c; -} -.btn-default:hover { - color: #333; - background-color: #e6e6e6; - border-color: #adadad; -} -.btn-default:active, -.btn-default.active, -.open > .dropdown-toggle.btn-default { - color: #333; - background-color: #e6e6e6; - border-color: #adadad; -} -.btn-default:active:hover, -.btn-default.active:hover, -.open > .dropdown-toggle.btn-default:hover, -.btn-default:active:focus, -.btn-default.active:focus, -.open > .dropdown-toggle.btn-default:focus, -.btn-default:active.focus, -.btn-default.active.focus, -.open > .dropdown-toggle.btn-default.focus { - color: #333; - background-color: #d4d4d4; - border-color: #8c8c8c; -} -.btn-default:active, -.btn-default.active, -.open > .dropdown-toggle.btn-default { - background-image: none; -} -.btn-default.disabled:hover, -.btn-default[disabled]:hover, -fieldset[disabled] .btn-default:hover, -.btn-default.disabled:focus, -.btn-default[disabled]:focus, -fieldset[disabled] .btn-default:focus, -.btn-default.disabled.focus, -.btn-default[disabled].focus, -fieldset[disabled] .btn-default.focus { - background-color: #fff; - border-color: #ccc; -} -.btn-default .badge { - color: #fff; - background-color: #333; -} -.btn-primary { - color: #fff; - background-color: #337ab7; - border-color: #2e6da4; -} -.btn-primary:focus, -.btn-primary.focus { - color: #fff; - background-color: #286090; - border-color: #122b40; -} -.btn-primary:hover { - color: #fff; - background-color: #286090; - border-color: #204d74; -} -.btn-primary:active, -.btn-primary.active, -.open > .dropdown-toggle.btn-primary { - color: #fff; - background-color: #286090; - border-color: #204d74; -} -.btn-primary:active:hover, -.btn-primary.active:hover, -.open > .dropdown-toggle.btn-primary:hover, -.btn-primary:active:focus, -.btn-primary.active:focus, -.open > .dropdown-toggle.btn-primary:focus, -.btn-primary:active.focus, -.btn-primary.active.focus, -.open > .dropdown-toggle.btn-primary.focus { - color: #fff; - background-color: #204d74; - border-color: #122b40; -} -.btn-primary:active, -.btn-primary.active, -.open > .dropdown-toggle.btn-primary { - background-image: none; -} -.btn-primary.disabled:hover, -.btn-primary[disabled]:hover, -fieldset[disabled] .btn-primary:hover, -.btn-primary.disabled:focus, -.btn-primary[disabled]:focus, -fieldset[disabled] .btn-primary:focus, -.btn-primary.disabled.focus, -.btn-primary[disabled].focus, -fieldset[disabled] .btn-primary.focus { - background-color: #337ab7; - border-color: #2e6da4; -} -.btn-primary .badge { - color: #337ab7; - background-color: #fff; -} -.btn-success { - color: #fff; - background-color: #5cb85c; - border-color: #4cae4c; -} -.btn-success:focus, -.btn-success.focus { - color: #fff; - background-color: #449d44; - border-color: #255625; -} -.btn-success:hover { - color: #fff; - background-color: #449d44; - border-color: #398439; -} -.btn-success:active, -.btn-success.active, -.open > .dropdown-toggle.btn-success { - color: #fff; - background-color: #449d44; - border-color: #398439; -} -.btn-success:active:hover, -.btn-success.active:hover, -.open > .dropdown-toggle.btn-success:hover, -.btn-success:active:focus, -.btn-success.active:focus, -.open > .dropdown-toggle.btn-success:focus, -.btn-success:active.focus, -.btn-success.active.focus, -.open > .dropdown-toggle.btn-success.focus { - color: #fff; - background-color: #398439; - border-color: #255625; -} -.btn-success:active, -.btn-success.active, -.open > .dropdown-toggle.btn-success { - background-image: none; -} -.btn-success.disabled:hover, -.btn-success[disabled]:hover, -fieldset[disabled] .btn-success:hover, -.btn-success.disabled:focus, -.btn-success[disabled]:focus, -fieldset[disabled] .btn-success:focus, -.btn-success.disabled.focus, -.btn-success[disabled].focus, -fieldset[disabled] .btn-success.focus { - background-color: #5cb85c; - border-color: #4cae4c; -} -.btn-success .badge { - color: #5cb85c; - background-color: #fff; -} -.btn-info { - color: #fff; - background-color: #5bc0de; - border-color: #46b8da; -} -.btn-info:focus, -.btn-info.focus { - color: #fff; - background-color: #31b0d5; - border-color: #1b6d85; -} -.btn-info:hover { - color: #fff; - background-color: #31b0d5; - border-color: #269abc; -} -.btn-info:active, -.btn-info.active, -.open > .dropdown-toggle.btn-info { - color: #fff; - background-color: #31b0d5; - border-color: #269abc; -} -.btn-info:active:hover, -.btn-info.active:hover, -.open > .dropdown-toggle.btn-info:hover, -.btn-info:active:focus, -.btn-info.active:focus, -.open > .dropdown-toggle.btn-info:focus, -.btn-info:active.focus, -.btn-info.active.focus, -.open > .dropdown-toggle.btn-info.focus { - color: #fff; - background-color: #269abc; - border-color: #1b6d85; -} -.btn-info:active, -.btn-info.active, -.open > .dropdown-toggle.btn-info { - background-image: none; -} -.btn-info.disabled:hover, -.btn-info[disabled]:hover, -fieldset[disabled] .btn-info:hover, -.btn-info.disabled:focus, -.btn-info[disabled]:focus, -fieldset[disabled] .btn-info:focus, -.btn-info.disabled.focus, -.btn-info[disabled].focus, -fieldset[disabled] .btn-info.focus { - background-color: #5bc0de; - border-color: #46b8da; -} -.btn-info .badge { - color: #5bc0de; - background-color: #fff; -} -.btn-warning { - color: #fff; - background-color: #f0ad4e; - border-color: #eea236; -} -.btn-warning:focus, -.btn-warning.focus { - color: #fff; - background-color: #ec971f; - border-color: #985f0d; -} -.btn-warning:hover { - color: #fff; - background-color: #ec971f; - border-color: #d58512; -} -.btn-warning:active, -.btn-warning.active, -.open > .dropdown-toggle.btn-warning { - color: #fff; - background-color: #ec971f; - border-color: #d58512; -} -.btn-warning:active:hover, -.btn-warning.active:hover, -.open > .dropdown-toggle.btn-warning:hover, -.btn-warning:active:focus, -.btn-warning.active:focus, -.open > .dropdown-toggle.btn-warning:focus, -.btn-warning:active.focus, -.btn-warning.active.focus, -.open > .dropdown-toggle.btn-warning.focus { - color: #fff; - background-color: #d58512; - border-color: #985f0d; -} -.btn-warning:active, -.btn-warning.active, -.open > .dropdown-toggle.btn-warning { - background-image: none; -} -.btn-warning.disabled:hover, -.btn-warning[disabled]:hover, -fieldset[disabled] .btn-warning:hover, -.btn-warning.disabled:focus, -.btn-warning[disabled]:focus, -fieldset[disabled] .btn-warning:focus, -.btn-warning.disabled.focus, -.btn-warning[disabled].focus, -fieldset[disabled] .btn-warning.focus { - background-color: #f0ad4e; - border-color: #eea236; -} -.btn-warning .badge { - color: #f0ad4e; - background-color: #fff; -} -.btn-danger { - color: #fff; - background-color: #d9534f; - border-color: #d43f3a; -} -.btn-danger:focus, -.btn-danger.focus { - color: #fff; - background-color: #c9302c; - border-color: #761c19; -} -.btn-danger:hover { - color: #fff; - background-color: #c9302c; - border-color: #ac2925; -} -.btn-danger:active, -.btn-danger.active, -.open > .dropdown-toggle.btn-danger { - color: #fff; - background-color: #c9302c; - border-color: #ac2925; -} -.btn-danger:active:hover, -.btn-danger.active:hover, -.open > .dropdown-toggle.btn-danger:hover, -.btn-danger:active:focus, -.btn-danger.active:focus, -.open > .dropdown-toggle.btn-danger:focus, -.btn-danger:active.focus, -.btn-danger.active.focus, -.open > .dropdown-toggle.btn-danger.focus { - color: #fff; - background-color: #ac2925; - border-color: #761c19; -} -.btn-danger:active, -.btn-danger.active, -.open > .dropdown-toggle.btn-danger { - background-image: none; -} -.btn-danger.disabled:hover, -.btn-danger[disabled]:hover, -fieldset[disabled] .btn-danger:hover, -.btn-danger.disabled:focus, -.btn-danger[disabled]:focus, -fieldset[disabled] .btn-danger:focus, -.btn-danger.disabled.focus, -.btn-danger[disabled].focus, -fieldset[disabled] .btn-danger.focus { - background-color: #d9534f; - border-color: #d43f3a; -} -.btn-danger .badge { - color: #d9534f; - background-color: #fff; -} -.btn-link { - font-weight: normal; - color: #337ab7; - border-radius: 0; -} -.btn-link, -.btn-link:active, -.btn-link.active, -.btn-link[disabled], -fieldset[disabled] .btn-link { - background-color: transparent; - -webkit-box-shadow: none; - box-shadow: none; -} -.btn-link, -.btn-link:hover, -.btn-link:focus, -.btn-link:active { - border-color: transparent; -} -.btn-link:hover, -.btn-link:focus { - color: #23527c; - text-decoration: underline; - background-color: transparent; -} -.btn-link[disabled]:hover, -fieldset[disabled] .btn-link:hover, -.btn-link[disabled]:focus, -fieldset[disabled] .btn-link:focus { - color: #777; - text-decoration: none; -} -.btn-lg, -.btn-group-lg > .btn { - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px; -} -.btn-sm, -.btn-group-sm > .btn { - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -.btn-xs, -.btn-group-xs > .btn { - padding: 1px 5px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -.btn-block { - display: block; - width: 100%; -} -.btn-block + .btn-block { - margin-top: 5px; -} -input[type="submit"].btn-block, -input[type="reset"].btn-block, -input[type="button"].btn-block { - width: 100%; -} -.fade { - opacity: 0; - -webkit-transition: opacity .15s linear; - -o-transition: opacity .15s linear; - transition: opacity .15s linear; -} -.fade.in { - opacity: 1; -} -.collapse { - display: none; -} -.collapse.in { - display: block; -} -tr.collapse.in { - display: table-row; -} -tbody.collapse.in { - display: table-row-group; -} -.collapsing { - position: relative; - height: 0; - overflow: hidden; - -webkit-transition-timing-function: ease; - -o-transition-timing-function: ease; - transition-timing-function: ease; - -webkit-transition-duration: .35s; - -o-transition-duration: .35s; - transition-duration: .35s; - -webkit-transition-property: height, visibility; - -o-transition-property: height, visibility; - transition-property: height, visibility; -} -.caret { - display: inline-block; - width: 0; - height: 0; - margin-left: 2px; - vertical-align: middle; - border-top: 4px dashed; - border-top: 4px solid \9; - border-right: 4px solid transparent; - border-left: 4px solid transparent; -} -.dropup, -.dropdown { - position: relative; -} -.dropdown-toggle:focus { - outline: 0; -} -.dropdown-menu { - position: absolute; - top: 100%; - left: 0; - z-index: 1000; - display: none; - float: left; - min-width: 160px; - padding: 5px 0; - margin: 2px 0 0; - font-size: 14px; - text-align: left; - list-style: none; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, .15); - border-radius: 4px; - -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); - box-shadow: 0 6px 12px rgba(0, 0, 0, .175); -} -.dropdown-menu.pull-right { - right: 0; - left: auto; -} -.dropdown-menu .divider { - height: 1px; - margin: 9px 0; - overflow: hidden; - background-color: #e5e5e5; -} -.dropdown-menu > li > a { - display: block; - padding: 3px 20px; - clear: both; - font-weight: normal; - line-height: 1.42857143; - color: #333; - white-space: nowrap; -} -.dropdown-menu > li > a:hover, -.dropdown-menu > li > a:focus { - color: #262626; - text-decoration: none; - background-color: #f5f5f5; -} -.dropdown-menu > .active > a, -.dropdown-menu > .active > a:hover, -.dropdown-menu > .active > a:focus { - color: #fff; - text-decoration: none; - background-color: #337ab7; - outline: 0; -} -.dropdown-menu > .disabled > a, -.dropdown-menu > .disabled > a:hover, -.dropdown-menu > .disabled > a:focus { - color: #777; -} -.dropdown-menu > .disabled > a:hover, -.dropdown-menu > .disabled > a:focus { - text-decoration: none; - cursor: not-allowed; - background-color: transparent; - background-image: none; - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); -} -.open > .dropdown-menu { - display: block; -} -.open > a { - outline: 0; -} -.dropdown-menu-right { - right: 0; - left: auto; -} -.dropdown-menu-left { - right: auto; - left: 0; -} -.dropdown-header { - display: block; - padding: 3px 20px; - font-size: 12px; - line-height: 1.42857143; - color: #777; - white-space: nowrap; -} -.dropdown-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 990; -} -.pull-right > .dropdown-menu { - right: 0; - left: auto; -} -.dropup .caret, -.navbar-fixed-bottom .dropdown .caret { - content: ""; - border-top: 0; - border-bottom: 4px dashed; - border-bottom: 4px solid \9; -} -.dropup .dropdown-menu, -.navbar-fixed-bottom .dropdown .dropdown-menu { - top: auto; - bottom: 100%; - margin-bottom: 2px; -} -@media (min-width: 768px) { - .navbar-right .dropdown-menu { - right: 0; - left: auto; - } - .navbar-right .dropdown-menu-left { - right: auto; - left: 0; - } -} -.btn-group, -.btn-group-vertical { - position: relative; - display: inline-block; - vertical-align: middle; -} -.btn-group > .btn, -.btn-group-vertical > .btn { - position: relative; - float: left; -} -.btn-group > .btn:hover, -.btn-group-vertical > .btn:hover, -.btn-group > .btn:focus, -.btn-group-vertical > .btn:focus, -.btn-group > .btn:active, -.btn-group-vertical > .btn:active, -.btn-group > .btn.active, -.btn-group-vertical > .btn.active { - z-index: 2; -} -.btn-group .btn + .btn, -.btn-group .btn + .btn-group, -.btn-group .btn-group + .btn, -.btn-group .btn-group + .btn-group { - margin-left: -1px; -} -.btn-toolbar { - margin-left: -5px; -} -.btn-toolbar .btn, -.btn-toolbar .btn-group, -.btn-toolbar .input-group { - float: left; -} -.btn-toolbar > .btn, -.btn-toolbar > .btn-group, -.btn-toolbar > .input-group { - margin-left: 5px; -} -.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { - border-radius: 0; -} -.btn-group > .btn:first-child { - margin-left: 0; -} -.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.btn-group > .btn:last-child:not(:first-child), -.btn-group > .dropdown-toggle:not(:first-child) { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} -.btn-group > .btn-group { - float: left; -} -.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { - border-radius: 0; -} -.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, -.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} -.btn-group .dropdown-toggle:active, -.btn-group.open .dropdown-toggle { - outline: 0; -} -.btn-group > .btn + .dropdown-toggle { - padding-right: 8px; - padding-left: 8px; -} -.btn-group > .btn-lg + .dropdown-toggle { - padding-right: 12px; - padding-left: 12px; -} -.btn-group.open .dropdown-toggle { - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); -} -.btn-group.open .dropdown-toggle.btn-link { - -webkit-box-shadow: none; - box-shadow: none; -} -.btn .caret { - margin-left: 0; -} -.btn-lg .caret { - border-width: 5px 5px 0; - border-bottom-width: 0; -} -.dropup .btn-lg .caret { - border-width: 0 5px 5px; -} -.btn-group-vertical > .btn, -.btn-group-vertical > .btn-group, -.btn-group-vertical > .btn-group > .btn { - display: block; - float: none; - width: 100%; - max-width: 100%; -} -.btn-group-vertical > .btn-group > .btn { - float: none; -} -.btn-group-vertical > .btn + .btn, -.btn-group-vertical > .btn + .btn-group, -.btn-group-vertical > .btn-group + .btn, -.btn-group-vertical > .btn-group + .btn-group { - margin-top: -1px; - margin-left: 0; -} -.btn-group-vertical > .btn:not(:first-child):not(:last-child) { - border-radius: 0; -} -.btn-group-vertical > .btn:first-child:not(:last-child) { - border-top-left-radius: 4px; - border-top-right-radius: 4px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} -.btn-group-vertical > .btn:last-child:not(:first-child) { - border-top-left-radius: 0; - border-top-right-radius: 0; - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px; -} -.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { - border-radius: 0; -} -.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, -.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} -.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { - border-top-left-radius: 0; - border-top-right-radius: 0; -} -.btn-group-justified { - display: table; - width: 100%; - table-layout: fixed; - border-collapse: separate; -} -.btn-group-justified > .btn, -.btn-group-justified > .btn-group { - display: table-cell; - float: none; - width: 1%; -} -.btn-group-justified > .btn-group .btn { - width: 100%; -} -.btn-group-justified > .btn-group .dropdown-menu { - left: auto; -} -[data-toggle="buttons"] > .btn input[type="radio"], -[data-toggle="buttons"] > .btn-group > .btn input[type="radio"], -[data-toggle="buttons"] > .btn input[type="checkbox"], -[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { - position: absolute; - clip: rect(0, 0, 0, 0); - pointer-events: none; -} -.input-group { - position: relative; - display: table; - border-collapse: separate; -} -.input-group[class*="col-"] { - float: none; - padding-right: 0; - padding-left: 0; -} -.input-group .form-control { - position: relative; - z-index: 2; - float: left; - width: 100%; - margin-bottom: 0; -} -.input-group .form-control:focus { - z-index: 3; -} -.input-group-lg > .form-control, -.input-group-lg > .input-group-addon, -.input-group-lg > .input-group-btn > .btn { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px; -} -select.input-group-lg > .form-control, -select.input-group-lg > .input-group-addon, -select.input-group-lg > .input-group-btn > .btn { - height: 46px; - line-height: 46px; -} -textarea.input-group-lg > .form-control, -textarea.input-group-lg > .input-group-addon, -textarea.input-group-lg > .input-group-btn > .btn, -select[multiple].input-group-lg > .form-control, -select[multiple].input-group-lg > .input-group-addon, -select[multiple].input-group-lg > .input-group-btn > .btn { - height: auto; -} -.input-group-sm > .form-control, -.input-group-sm > .input-group-addon, -.input-group-sm > .input-group-btn > .btn { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -select.input-group-sm > .form-control, -select.input-group-sm > .input-group-addon, -select.input-group-sm > .input-group-btn > .btn { - height: 30px; - line-height: 30px; -} -textarea.input-group-sm > .form-control, -textarea.input-group-sm > .input-group-addon, -textarea.input-group-sm > .input-group-btn > .btn, -select[multiple].input-group-sm > .form-control, -select[multiple].input-group-sm > .input-group-addon, -select[multiple].input-group-sm > .input-group-btn > .btn { - height: auto; -} -.input-group-addon, -.input-group-btn, -.input-group .form-control { - display: table-cell; -} -.input-group-addon:not(:first-child):not(:last-child), -.input-group-btn:not(:first-child):not(:last-child), -.input-group .form-control:not(:first-child):not(:last-child) { - border-radius: 0; -} -.input-group-addon, -.input-group-btn { - width: 1%; - white-space: nowrap; - vertical-align: middle; -} -.input-group-addon { - padding: 6px 12px; - font-size: 14px; - font-weight: normal; - line-height: 1; - color: #555; - text-align: center; - background-color: #eee; - border: 1px solid #ccc; - border-radius: 4px; -} -.input-group-addon.input-sm { - padding: 5px 10px; - font-size: 12px; - border-radius: 3px; -} -.input-group-addon.input-lg { - padding: 10px 16px; - font-size: 18px; - border-radius: 6px; -} -.input-group-addon input[type="radio"], -.input-group-addon input[type="checkbox"] { - margin-top: 0; -} -.input-group .form-control:first-child, -.input-group-addon:first-child, -.input-group-btn:first-child > .btn, -.input-group-btn:first-child > .btn-group > .btn, -.input-group-btn:first-child > .dropdown-toggle, -.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), -.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.input-group-addon:first-child { - border-right: 0; -} -.input-group .form-control:last-child, -.input-group-addon:last-child, -.input-group-btn:last-child > .btn, -.input-group-btn:last-child > .btn-group > .btn, -.input-group-btn:last-child > .dropdown-toggle, -.input-group-btn:first-child > .btn:not(:first-child), -.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} -.input-group-addon:last-child { - border-left: 0; -} -.input-group-btn { - position: relative; - font-size: 0; - white-space: nowrap; -} -.input-group-btn > .btn { - position: relative; -} -.input-group-btn > .btn + .btn { - margin-left: -1px; -} -.input-group-btn > .btn:hover, -.input-group-btn > .btn:focus, -.input-group-btn > .btn:active { - z-index: 2; -} -.input-group-btn:first-child > .btn, -.input-group-btn:first-child > .btn-group { - margin-right: -1px; -} -.input-group-btn:last-child > .btn, -.input-group-btn:last-child > .btn-group { - z-index: 2; - margin-left: -1px; -} -.nav { - padding-left: 0; - margin-bottom: 0; - list-style: none; -} -.nav > li { - position: relative; - display: block; -} -.nav > li > a { - position: relative; - display: block; - padding: 10px 15px; -} -.nav > li > a:hover, -.nav > li > a:focus { - text-decoration: none; - background-color: #eee; -} -.nav > li.disabled > a { - color: #777; -} -.nav > li.disabled > a:hover, -.nav > li.disabled > a:focus { - color: #777; - text-decoration: none; - cursor: not-allowed; - background-color: transparent; -} -.nav .open > a, -.nav .open > a:hover, -.nav .open > a:focus { - background-color: #eee; - border-color: #337ab7; -} -.nav .nav-divider { - height: 1px; - margin: 9px 0; - overflow: hidden; - background-color: #e5e5e5; -} -.nav > li > a > img { - max-width: none; -} -.nav-tabs { - border-bottom: 1px solid #ddd; -} -.nav-tabs > li { - float: left; - margin-bottom: -1px; -} -.nav-tabs > li > a { - margin-right: 2px; - line-height: 1.42857143; - border: 1px solid transparent; - border-radius: 4px 4px 0 0; -} -.nav-tabs > li > a:hover { - border-color: #eee #eee #ddd; -} -.nav-tabs > li.active > a, -.nav-tabs > li.active > a:hover, -.nav-tabs > li.active > a:focus { - color: #555; - cursor: default; - background-color: #fff; - border: 1px solid #ddd; - border-bottom-color: transparent; -} -.nav-tabs.nav-justified { - width: 100%; - border-bottom: 0; -} -.nav-tabs.nav-justified > li { - float: none; -} -.nav-tabs.nav-justified > li > a { - margin-bottom: 5px; - text-align: center; -} -.nav-tabs.nav-justified > .dropdown .dropdown-menu { - top: auto; - left: auto; -} -@media (min-width: 768px) { - .nav-tabs.nav-justified > li { - display: table-cell; - width: 1%; - } - .nav-tabs.nav-justified > li > a { - margin-bottom: 0; - } -} -.nav-tabs.nav-justified > li > a { - margin-right: 0; - border-radius: 4px; -} -.nav-tabs.nav-justified > .active > a, -.nav-tabs.nav-justified > .active > a:hover, -.nav-tabs.nav-justified > .active > a:focus { - border: 1px solid #ddd; -} -@media (min-width: 768px) { - .nav-tabs.nav-justified > li > a { - border-bottom: 1px solid #ddd; - border-radius: 4px 4px 0 0; - } - .nav-tabs.nav-justified > .active > a, - .nav-tabs.nav-justified > .active > a:hover, - .nav-tabs.nav-justified > .active > a:focus { - border-bottom-color: #fff; - } -} -.nav-pills > li { - float: left; -} -.nav-pills > li > a { - border-radius: 4px; -} -.nav-pills > li + li { - margin-left: 2px; -} -.nav-pills > li.active > a, -.nav-pills > li.active > a:hover, -.nav-pills > li.active > a:focus { - color: #fff; - background-color: #337ab7; -} -.nav-stacked > li { - float: none; -} -.nav-stacked > li + li { - margin-top: 2px; - margin-left: 0; -} -.nav-justified { - width: 100%; -} -.nav-justified > li { - float: none; -} -.nav-justified > li > a { - margin-bottom: 5px; - text-align: center; -} -.nav-justified > .dropdown .dropdown-menu { - top: auto; - left: auto; -} -@media (min-width: 768px) { - .nav-justified > li { - display: table-cell; - width: 1%; - } - .nav-justified > li > a { - margin-bottom: 0; - } -} -.nav-tabs-justified { - border-bottom: 0; -} -.nav-tabs-justified > li > a { - margin-right: 0; - border-radius: 4px; -} -.nav-tabs-justified > .active > a, -.nav-tabs-justified > .active > a:hover, -.nav-tabs-justified > .active > a:focus { - border: 1px solid #ddd; -} -@media (min-width: 768px) { - .nav-tabs-justified > li > a { - border-bottom: 1px solid #ddd; - border-radius: 4px 4px 0 0; - } - .nav-tabs-justified > .active > a, - .nav-tabs-justified > .active > a:hover, - .nav-tabs-justified > .active > a:focus { - border-bottom-color: #fff; - } -} -.tab-content > .tab-pane { - display: none; -} -.tab-content > .active { - display: block; -} -.nav-tabs .dropdown-menu { - margin-top: -1px; - border-top-left-radius: 0; - border-top-right-radius: 0; -} -.navbar { - position: relative; - min-height: 50px; - margin-bottom: 20px; - border: 1px solid transparent; -} -@media (min-width: 768px) { - .navbar { - border-radius: 4px; - } -} -@media (min-width: 768px) { - .navbar-header { - float: left; - } -} -.navbar-collapse { - padding-right: 15px; - padding-left: 15px; - overflow-x: visible; - -webkit-overflow-scrolling: touch; - border-top: 1px solid transparent; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); -} -.navbar-collapse.in { - overflow-y: auto; -} -@media (min-width: 768px) { - .navbar-collapse { - width: auto; - border-top: 0; - -webkit-box-shadow: none; - box-shadow: none; - } - .navbar-collapse.collapse { - display: block !important; - height: auto !important; - padding-bottom: 0; - overflow: visible !important; - } - .navbar-collapse.in { - overflow-y: visible; - } - .navbar-fixed-top .navbar-collapse, - .navbar-static-top .navbar-collapse, - .navbar-fixed-bottom .navbar-collapse { - padding-right: 0; - padding-left: 0; - } -} -.navbar-fixed-top .navbar-collapse, -.navbar-fixed-bottom .navbar-collapse { - max-height: 340px; -} -@media (max-device-width: 480px) and (orientation: landscape) { - .navbar-fixed-top .navbar-collapse, - .navbar-fixed-bottom .navbar-collapse { - max-height: 200px; - } -} -.container > .navbar-header, -.container-fluid > .navbar-header, -.container > .navbar-collapse, -.container-fluid > .navbar-collapse { - margin-right: -15px; - margin-left: -15px; -} -@media (min-width: 768px) { - .container > .navbar-header, - .container-fluid > .navbar-header, - .container > .navbar-collapse, - .container-fluid > .navbar-collapse { - margin-right: 0; - margin-left: 0; - } -} -.navbar-static-top { - z-index: 1000; - border-width: 0 0 1px; -} -@media (min-width: 768px) { - .navbar-static-top { - border-radius: 0; - } -} -.navbar-fixed-top, -.navbar-fixed-bottom { - position: fixed; - right: 0; - left: 0; - z-index: 1030; -} -@media (min-width: 768px) { - .navbar-fixed-top, - .navbar-fixed-bottom { - border-radius: 0; - } -} -.navbar-fixed-top { - top: 0; - border-width: 0 0 1px; -} -.navbar-fixed-bottom { - bottom: 0; - margin-bottom: 0; - border-width: 1px 0 0; -} -.navbar-brand { - float: left; - height: 50px; - padding: 15px 15px; - font-size: 18px; - line-height: 20px; -} -.navbar-brand:hover, -.navbar-brand:focus { - text-decoration: none; -} -.navbar-brand > img { - display: block; -} -@media (min-width: 768px) { - .navbar > .container .navbar-brand, - .navbar > .container-fluid .navbar-brand { - margin-left: -15px; - } -} -.navbar-toggle { - position: relative; - float: right; - padding: 9px 10px; - margin-top: 8px; - margin-right: 15px; - margin-bottom: 8px; - background-color: transparent; - background-image: none; - border: 1px solid transparent; - border-radius: 4px; -} -.navbar-toggle:focus { - outline: 0; -} -.navbar-toggle .icon-bar { - display: block; - width: 22px; - height: 2px; - border-radius: 1px; -} -.navbar-toggle .icon-bar + .icon-bar { - margin-top: 4px; -} -@media (min-width: 768px) { - .navbar-toggle { - display: none; - } -} -.navbar-nav { - margin: 7.5px -15px; -} -.navbar-nav > li > a { - padding-top: 10px; - padding-bottom: 10px; - line-height: 20px; -} -@media (max-width: 767px) { - .navbar-nav .open .dropdown-menu { - position: static; - float: none; - width: auto; - margin-top: 0; - background-color: transparent; - border: 0; - -webkit-box-shadow: none; - box-shadow: none; - } - .navbar-nav .open .dropdown-menu > li > a, - .navbar-nav .open .dropdown-menu .dropdown-header { - padding: 5px 15px 5px 25px; - } - .navbar-nav .open .dropdown-menu > li > a { - line-height: 20px; - } - .navbar-nav .open .dropdown-menu > li > a:hover, - .navbar-nav .open .dropdown-menu > li > a:focus { - background-image: none; - } -} -@media (min-width: 768px) { - .navbar-nav { - float: left; - margin: 0; - } - .navbar-nav > li { - float: left; - } - .navbar-nav > li > a { - padding-top: 15px; - padding-bottom: 15px; - } -} -.navbar-form { - padding: 10px 15px; - margin-top: 8px; - margin-right: -15px; - margin-bottom: 8px; - margin-left: -15px; - border-top: 1px solid transparent; - border-bottom: 1px solid transparent; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); -} -@media (min-width: 768px) { - .navbar-form .form-group { - display: inline-block; - margin-bottom: 0; - vertical-align: middle; - } - .navbar-form .form-control { - display: inline-block; - width: auto; - vertical-align: middle; - } - .navbar-form .form-control-static { - display: inline-block; - } - .navbar-form .input-group { - display: inline-table; - vertical-align: middle; - } - .navbar-form .input-group .input-group-addon, - .navbar-form .input-group .input-group-btn, - .navbar-form .input-group .form-control { - width: auto; - } - .navbar-form .input-group > .form-control { - width: 100%; - } - .navbar-form .control-label { - margin-bottom: 0; - vertical-align: middle; - } - .navbar-form .radio, - .navbar-form .checkbox { - display: inline-block; - margin-top: 0; - margin-bottom: 0; - vertical-align: middle; - } - .navbar-form .radio label, - .navbar-form .checkbox label { - padding-left: 0; - } - .navbar-form .radio input[type="radio"], - .navbar-form .checkbox input[type="checkbox"] { - position: relative; - margin-left: 0; - } - .navbar-form .has-feedback .form-control-feedback { - top: 0; - } -} -@media (max-width: 767px) { - .navbar-form .form-group { - margin-bottom: 5px; - } - .navbar-form .form-group:last-child { - margin-bottom: 0; - } -} -@media (min-width: 768px) { - .navbar-form { - width: auto; - padding-top: 0; - padding-bottom: 0; - margin-right: 0; - margin-left: 0; - border: 0; - -webkit-box-shadow: none; - box-shadow: none; - } -} -.navbar-nav > li > .dropdown-menu { - margin-top: 0; - border-top-left-radius: 0; - border-top-right-radius: 0; -} -.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { - margin-bottom: 0; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} -.navbar-btn { - margin-top: 8px; - margin-bottom: 8px; -} -.navbar-btn.btn-sm { - margin-top: 10px; - margin-bottom: 10px; -} -.navbar-btn.btn-xs { - margin-top: 14px; - margin-bottom: 14px; -} -.navbar-text { - margin-top: 15px; - margin-bottom: 15px; -} -@media (min-width: 768px) { - .navbar-text { - float: left; - margin-right: 15px; - margin-left: 15px; - } -} -@media (min-width: 768px) { - .navbar-left { - float: left !important; - } - .navbar-right { - float: right !important; - margin-right: -15px; - } - .navbar-right ~ .navbar-right { - margin-right: 0; - } -} -.navbar-default { - background-color: #f8f8f8; - border-color: #e7e7e7; -} -.navbar-default .navbar-brand { - color: #777; -} -.navbar-default .navbar-brand:hover, -.navbar-default .navbar-brand:focus { - color: #5e5e5e; - background-color: transparent; -} -.navbar-default .navbar-text { - color: #777; -} -.navbar-default .navbar-nav > li > a { - color: #777; -} -.navbar-default .navbar-nav > li > a:hover, -.navbar-default .navbar-nav > li > a:focus { - color: #333; - background-color: transparent; -} -.navbar-default .navbar-nav > .active > a, -.navbar-default .navbar-nav > .active > a:hover, -.navbar-default .navbar-nav > .active > a:focus { - color: #555; - background-color: #e7e7e7; -} -.navbar-default .navbar-nav > .disabled > a, -.navbar-default .navbar-nav > .disabled > a:hover, -.navbar-default .navbar-nav > .disabled > a:focus { - color: #ccc; - background-color: transparent; -} -.navbar-default .navbar-toggle { - border-color: #ddd; -} -.navbar-default .navbar-toggle:hover, -.navbar-default .navbar-toggle:focus { - background-color: #ddd; -} -.navbar-default .navbar-toggle .icon-bar { - background-color: #888; -} -.navbar-default .navbar-collapse, -.navbar-default .navbar-form { - border-color: #e7e7e7; -} -.navbar-default .navbar-nav > .open > a, -.navbar-default .navbar-nav > .open > a:hover, -.navbar-default .navbar-nav > .open > a:focus { - color: #555; - background-color: #e7e7e7; -} -@media (max-width: 767px) { - .navbar-default .navbar-nav .open .dropdown-menu > li > a { - color: #777; - } - .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, - .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { - color: #333; - background-color: transparent; - } - .navbar-default .navbar-nav .open .dropdown-menu > .active > a, - .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, - .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { - color: #555; - background-color: #e7e7e7; - } - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { - color: #ccc; - background-color: transparent; - } -} -.navbar-default .navbar-link { - color: #777; -} -.navbar-default .navbar-link:hover { - color: #333; -} -.navbar-default .btn-link { - color: #777; -} -.navbar-default .btn-link:hover, -.navbar-default .btn-link:focus { - color: #333; -} -.navbar-default .btn-link[disabled]:hover, -fieldset[disabled] .navbar-default .btn-link:hover, -.navbar-default .btn-link[disabled]:focus, -fieldset[disabled] .navbar-default .btn-link:focus { - color: #ccc; -} -.navbar-inverse { - background-color: #222; - border-color: #080808; -} -.navbar-inverse .navbar-brand { - color: #9d9d9d; -} -.navbar-inverse .navbar-brand:hover, -.navbar-inverse .navbar-brand:focus { - color: #fff; - background-color: transparent; -} -.navbar-inverse .navbar-text { - color: #9d9d9d; -} -.navbar-inverse .navbar-nav > li > a { - color: #9d9d9d; -} -.navbar-inverse .navbar-nav > li > a:hover, -.navbar-inverse .navbar-nav > li > a:focus { - color: #fff; - background-color: transparent; -} -.navbar-inverse .navbar-nav > .active > a, -.navbar-inverse .navbar-nav > .active > a:hover, -.navbar-inverse .navbar-nav > .active > a:focus { - color: #fff; - background-color: #080808; -} -.navbar-inverse .navbar-nav > .disabled > a, -.navbar-inverse .navbar-nav > .disabled > a:hover, -.navbar-inverse .navbar-nav > .disabled > a:focus { - color: #444; - background-color: transparent; -} -.navbar-inverse .navbar-toggle { - border-color: #333; -} -.navbar-inverse .navbar-toggle:hover, -.navbar-inverse .navbar-toggle:focus { - background-color: #333; -} -.navbar-inverse .navbar-toggle .icon-bar { - background-color: #fff; -} -.navbar-inverse .navbar-collapse, -.navbar-inverse .navbar-form { - border-color: #101010; -} -.navbar-inverse .navbar-nav > .open > a, -.navbar-inverse .navbar-nav > .open > a:hover, -.navbar-inverse .navbar-nav > .open > a:focus { - color: #fff; - background-color: #080808; -} -@media (max-width: 767px) { - .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { - border-color: #080808; - } - .navbar-inverse .navbar-nav .open .dropdown-menu .divider { - background-color: #080808; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { - color: #9d9d9d; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { - color: #fff; - background-color: transparent; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { - color: #fff; - background-color: #080808; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { - color: #444; - background-color: transparent; - } -} -.navbar-inverse .navbar-link { - color: #9d9d9d; -} -.navbar-inverse .navbar-link:hover { - color: #fff; -} -.navbar-inverse .btn-link { - color: #9d9d9d; -} -.navbar-inverse .btn-link:hover, -.navbar-inverse .btn-link:focus { - color: #fff; -} -.navbar-inverse .btn-link[disabled]:hover, -fieldset[disabled] .navbar-inverse .btn-link:hover, -.navbar-inverse .btn-link[disabled]:focus, -fieldset[disabled] .navbar-inverse .btn-link:focus { - color: #444; -} -.breadcrumb { - padding: 8px 15px; - margin-bottom: 20px; - list-style: none; - background-color: #f5f5f5; - border-radius: 4px; -} -.breadcrumb > li { - display: inline-block; -} -.breadcrumb > li + li:before { - padding: 0 5px; - color: #ccc; - content: "/\00a0"; -} -.breadcrumb > .active { - color: #777; -} -.pagination { - display: inline-block; - padding-left: 0; - margin: 20px 0; - border-radius: 4px; -} -.pagination > li { - display: inline; -} -.pagination > li > a, -.pagination > li > span { - position: relative; - float: left; - padding: 6px 12px; - margin-left: -1px; - line-height: 1.42857143; - color: #337ab7; - text-decoration: none; - background-color: #fff; - border: 1px solid #ddd; -} -.pagination > li:first-child > a, -.pagination > li:first-child > span { - margin-left: 0; - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; -} -.pagination > li:last-child > a, -.pagination > li:last-child > span { - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; -} -.pagination > li > a:hover, -.pagination > li > span:hover, -.pagination > li > a:focus, -.pagination > li > span:focus { - z-index: 2; - color: #23527c; - background-color: #eee; - border-color: #ddd; -} -.pagination > .active > a, -.pagination > .active > span, -.pagination > .active > a:hover, -.pagination > .active > span:hover, -.pagination > .active > a:focus, -.pagination > .active > span:focus { - z-index: 3; - color: #fff; - cursor: default; - background-color: #337ab7; - border-color: #337ab7; -} -.pagination > .disabled > span, -.pagination > .disabled > span:hover, -.pagination > .disabled > span:focus, -.pagination > .disabled > a, -.pagination > .disabled > a:hover, -.pagination > .disabled > a:focus { - color: #777; - cursor: not-allowed; - background-color: #fff; - border-color: #ddd; -} -.pagination-lg > li > a, -.pagination-lg > li > span { - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; -} -.pagination-lg > li:first-child > a, -.pagination-lg > li:first-child > span { - border-top-left-radius: 6px; - border-bottom-left-radius: 6px; -} -.pagination-lg > li:last-child > a, -.pagination-lg > li:last-child > span { - border-top-right-radius: 6px; - border-bottom-right-radius: 6px; -} -.pagination-sm > li > a, -.pagination-sm > li > span { - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; -} -.pagination-sm > li:first-child > a, -.pagination-sm > li:first-child > span { - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; -} -.pagination-sm > li:last-child > a, -.pagination-sm > li:last-child > span { - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; -} -.pager { - padding-left: 0; - margin: 20px 0; - text-align: center; - list-style: none; -} -.pager li { - display: inline; -} -.pager li > a, -.pager li > span { - display: inline-block; - padding: 5px 14px; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 15px; -} -.pager li > a:hover, -.pager li > a:focus { - text-decoration: none; - background-color: #eee; -} -.pager .next > a, -.pager .next > span { - float: right; -} -.pager .previous > a, -.pager .previous > span { - float: left; -} -.pager .disabled > a, -.pager .disabled > a:hover, -.pager .disabled > a:focus, -.pager .disabled > span { - color: #777; - cursor: not-allowed; - background-color: #fff; -} -.label { - display: inline; - padding: .2em .6em .3em; - font-size: 75%; - font-weight: bold; - line-height: 1; - color: #fff; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - border-radius: .25em; -} -a.label:hover, -a.label:focus { - color: #fff; - text-decoration: none; - cursor: pointer; -} -.label:empty { - display: none; -} -.btn .label { - position: relative; - top: -1px; -} -.label-default { - background-color: #777; -} -.label-default[href]:hover, -.label-default[href]:focus { - background-color: #5e5e5e; -} -.label-primary { - background-color: #337ab7; -} -.label-primary[href]:hover, -.label-primary[href]:focus { - background-color: #286090; -} -.label-success { - background-color: #5cb85c; -} -.label-success[href]:hover, -.label-success[href]:focus { - background-color: #449d44; -} -.label-info { - background-color: #5bc0de; -} -.label-info[href]:hover, -.label-info[href]:focus { - background-color: #31b0d5; -} -.label-warning { - background-color: #f0ad4e; -} -.label-warning[href]:hover, -.label-warning[href]:focus { - background-color: #ec971f; -} -.label-danger { - background-color: #d9534f; -} -.label-danger[href]:hover, -.label-danger[href]:focus { - background-color: #c9302c; -} -.badge { - display: inline-block; - min-width: 10px; - padding: 3px 7px; - font-size: 12px; - font-weight: bold; - line-height: 1; - color: #fff; - text-align: center; - white-space: nowrap; - vertical-align: middle; - background-color: #777; - border-radius: 10px; -} -.badge:empty { - display: none; -} -.btn .badge { - position: relative; - top: -1px; -} -.btn-xs .badge, -.btn-group-xs > .btn .badge { - top: 0; - padding: 1px 5px; -} -a.badge:hover, -a.badge:focus { - color: #fff; - text-decoration: none; - cursor: pointer; -} -.list-group-item.active > .badge, -.nav-pills > .active > a > .badge { - color: #337ab7; - background-color: #fff; -} -.list-group-item > .badge { - float: right; -} -.list-group-item > .badge + .badge { - margin-right: 5px; -} -.nav-pills > li > a > .badge { - margin-left: 3px; -} -.jumbotron { - padding-top: 30px; - padding-bottom: 30px; - margin-bottom: 30px; - color: inherit; - background-color: #eee; -} -.jumbotron h1, -.jumbotron .h1 { - color: inherit; -} -.jumbotron p { - margin-bottom: 15px; - font-size: 21px; - font-weight: 200; -} -.jumbotron > hr { - border-top-color: #d5d5d5; -} -.container .jumbotron, -.container-fluid .jumbotron { - padding-right: 15px; - padding-left: 15px; - border-radius: 6px; -} -.jumbotron .container { - max-width: 100%; -} -@media screen and (min-width: 768px) { - .jumbotron { - padding-top: 48px; - padding-bottom: 48px; - } - .container .jumbotron, - .container-fluid .jumbotron { - padding-right: 60px; - padding-left: 60px; - } - .jumbotron h1, - .jumbotron .h1 { - font-size: 63px; - } -} -.thumbnail { - display: block; - padding: 4px; - margin-bottom: 20px; - line-height: 1.42857143; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 4px; - -webkit-transition: border .2s ease-in-out; - -o-transition: border .2s ease-in-out; - transition: border .2s ease-in-out; -} -.thumbnail > img, -.thumbnail a > img { - margin-right: auto; - margin-left: auto; -} -a.thumbnail:hover, -a.thumbnail:focus, -a.thumbnail.active { - border-color: #337ab7; -} -.thumbnail .caption { - padding: 9px; - color: #333; -} -.alert { - padding: 15px; - margin-bottom: 20px; - border: 1px solid transparent; - border-radius: 4px; -} -.alert h4 { - margin-top: 0; - color: inherit; -} -.alert .alert-link { - font-weight: bold; -} -.alert > p, -.alert > ul { - margin-bottom: 0; -} -.alert > p + p { - margin-top: 5px; -} -.alert-dismissable, -.alert-dismissible { - padding-right: 35px; -} -.alert-dismissable .close, -.alert-dismissible .close { - position: relative; - top: -2px; - right: -21px; - color: inherit; -} -.alert-success { - color: #3c763d; - background-color: #dff0d8; - border-color: #d6e9c6; -} -.alert-success hr { - border-top-color: #c9e2b3; -} -.alert-success .alert-link { - color: #2b542c; -} -.alert-info { - color: #31708f; - background-color: #d9edf7; - border-color: #bce8f1; -} -.alert-info hr { - border-top-color: #a6e1ec; -} -.alert-info .alert-link { - color: #245269; -} -.alert-warning { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #faebcc; -} -.alert-warning hr { - border-top-color: #f7e1b5; -} -.alert-warning .alert-link { - color: #66512c; -} -.alert-danger { - color: #a94442; - background-color: #f2dede; - border-color: #ebccd1; -} -.alert-danger hr { - border-top-color: #e4b9c0; -} -.alert-danger .alert-link { - color: #843534; -} -@-webkit-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} -@-o-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} -@keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} -.progress { - height: 20px; - margin-bottom: 20px; - overflow: hidden; - background-color: #f5f5f5; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); -} -.progress-bar { - float: left; - width: 0; - height: 100%; - font-size: 12px; - line-height: 20px; - color: #fff; - text-align: center; - background-color: #337ab7; - -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); - -webkit-transition: width .6s ease; - -o-transition: width .6s ease; - transition: width .6s ease; -} -.progress-striped .progress-bar, -.progress-bar-striped { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - -webkit-background-size: 40px 40px; - background-size: 40px 40px; -} -.progress.active .progress-bar, -.progress-bar.active { - -webkit-animation: progress-bar-stripes 2s linear infinite; - -o-animation: progress-bar-stripes 2s linear infinite; - animation: progress-bar-stripes 2s linear infinite; -} -.progress-bar-success { - background-color: #5cb85c; -} -.progress-striped .progress-bar-success { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -} -.progress-bar-info { - background-color: #5bc0de; -} -.progress-striped .progress-bar-info { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -} -.progress-bar-warning { - background-color: #f0ad4e; -} -.progress-striped .progress-bar-warning { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -} -.progress-bar-danger { - background-color: #d9534f; -} -.progress-striped .progress-bar-danger { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -} -.media { - margin-top: 15px; -} -.media:first-child { - margin-top: 0; -} -.media, -.media-body { - overflow: hidden; - zoom: 1; -} -.media-body { - width: 10000px; -} -.media-object { - display: block; -} -.media-object.img-thumbnail { - max-width: none; -} -.media-right, -.media > .pull-right { - padding-left: 10px; -} -.media-left, -.media > .pull-left { - padding-right: 10px; -} -.media-left, -.media-right, -.media-body { - display: table-cell; - vertical-align: top; -} -.media-middle { - vertical-align: middle; -} -.media-bottom { - vertical-align: bottom; -} -.media-heading { - margin-top: 0; - margin-bottom: 5px; -} -.media-list { - padding-left: 0; - list-style: none; -} -.list-group { - padding-left: 0; - margin-bottom: 20px; -} -.list-group-item { - position: relative; - display: block; - padding: 10px 15px; - margin-bottom: -1px; - background-color: #fff; - border: 1px solid #ddd; -} -.list-group-item:first-child { - border-top-left-radius: 4px; - border-top-right-radius: 4px; -} -.list-group-item:last-child { - margin-bottom: 0; - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px; -} -a.list-group-item, -button.list-group-item { - color: #555; -} -a.list-group-item .list-group-item-heading, -button.list-group-item .list-group-item-heading { - color: #333; -} -a.list-group-item:hover, -button.list-group-item:hover, -a.list-group-item:focus, -button.list-group-item:focus { - color: #555; - text-decoration: none; - background-color: #f5f5f5; -} -button.list-group-item { - width: 100%; - text-align: left; -} -.list-group-item.disabled, -.list-group-item.disabled:hover, -.list-group-item.disabled:focus { - color: #777; - cursor: not-allowed; - background-color: #eee; -} -.list-group-item.disabled .list-group-item-heading, -.list-group-item.disabled:hover .list-group-item-heading, -.list-group-item.disabled:focus .list-group-item-heading { - color: inherit; -} -.list-group-item.disabled .list-group-item-text, -.list-group-item.disabled:hover .list-group-item-text, -.list-group-item.disabled:focus .list-group-item-text { - color: #777; -} -.list-group-item.active, -.list-group-item.active:hover, -.list-group-item.active:focus { - z-index: 2; - color: #fff; - background-color: #337ab7; - border-color: #337ab7; -} -.list-group-item.active .list-group-item-heading, -.list-group-item.active:hover .list-group-item-heading, -.list-group-item.active:focus .list-group-item-heading, -.list-group-item.active .list-group-item-heading > small, -.list-group-item.active:hover .list-group-item-heading > small, -.list-group-item.active:focus .list-group-item-heading > small, -.list-group-item.active .list-group-item-heading > .small, -.list-group-item.active:hover .list-group-item-heading > .small, -.list-group-item.active:focus .list-group-item-heading > .small { - color: inherit; -} -.list-group-item.active .list-group-item-text, -.list-group-item.active:hover .list-group-item-text, -.list-group-item.active:focus .list-group-item-text { - color: #c7ddef; -} -.list-group-item-success { - color: #3c763d; - background-color: #dff0d8; -} -a.list-group-item-success, -button.list-group-item-success { - color: #3c763d; -} -a.list-group-item-success .list-group-item-heading, -button.list-group-item-success .list-group-item-heading { - color: inherit; -} -a.list-group-item-success:hover, -button.list-group-item-success:hover, -a.list-group-item-success:focus, -button.list-group-item-success:focus { - color: #3c763d; - background-color: #d0e9c6; -} -a.list-group-item-success.active, -button.list-group-item-success.active, -a.list-group-item-success.active:hover, -button.list-group-item-success.active:hover, -a.list-group-item-success.active:focus, -button.list-group-item-success.active:focus { - color: #fff; - background-color: #3c763d; - border-color: #3c763d; -} -.list-group-item-info { - color: #31708f; - background-color: #d9edf7; -} -a.list-group-item-info, -button.list-group-item-info { - color: #31708f; -} -a.list-group-item-info .list-group-item-heading, -button.list-group-item-info .list-group-item-heading { - color: inherit; -} -a.list-group-item-info:hover, -button.list-group-item-info:hover, -a.list-group-item-info:focus, -button.list-group-item-info:focus { - color: #31708f; - background-color: #c4e3f3; -} -a.list-group-item-info.active, -button.list-group-item-info.active, -a.list-group-item-info.active:hover, -button.list-group-item-info.active:hover, -a.list-group-item-info.active:focus, -button.list-group-item-info.active:focus { - color: #fff; - background-color: #31708f; - border-color: #31708f; -} -.list-group-item-warning { - color: #8a6d3b; - background-color: #fcf8e3; -} -a.list-group-item-warning, -button.list-group-item-warning { - color: #8a6d3b; -} -a.list-group-item-warning .list-group-item-heading, -button.list-group-item-warning .list-group-item-heading { - color: inherit; -} -a.list-group-item-warning:hover, -button.list-group-item-warning:hover, -a.list-group-item-warning:focus, -button.list-group-item-warning:focus { - color: #8a6d3b; - background-color: #faf2cc; -} -a.list-group-item-warning.active, -button.list-group-item-warning.active, -a.list-group-item-warning.active:hover, -button.list-group-item-warning.active:hover, -a.list-group-item-warning.active:focus, -button.list-group-item-warning.active:focus { - color: #fff; - background-color: #8a6d3b; - border-color: #8a6d3b; -} -.list-group-item-danger { - color: #a94442; - background-color: #f2dede; -} -a.list-group-item-danger, -button.list-group-item-danger { - color: #a94442; -} -a.list-group-item-danger .list-group-item-heading, -button.list-group-item-danger .list-group-item-heading { - color: inherit; -} -a.list-group-item-danger:hover, -button.list-group-item-danger:hover, -a.list-group-item-danger:focus, -button.list-group-item-danger:focus { - color: #a94442; - background-color: #ebcccc; -} -a.list-group-item-danger.active, -button.list-group-item-danger.active, -a.list-group-item-danger.active:hover, -button.list-group-item-danger.active:hover, -a.list-group-item-danger.active:focus, -button.list-group-item-danger.active:focus { - color: #fff; - background-color: #a94442; - border-color: #a94442; -} -.list-group-item-heading { - margin-top: 0; - margin-bottom: 5px; -} -.list-group-item-text { - margin-bottom: 0; - line-height: 1.3; -} -.panel { - margin-bottom: 20px; - background-color: #fff; - border: 1px solid transparent; - border-radius: 4px; - -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); - box-shadow: 0 1px 1px rgba(0, 0, 0, .05); -} -.panel-body { - padding: 15px; -} -.panel-heading { - padding: 10px 15px; - border-bottom: 1px solid transparent; - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} -.panel-heading > .dropdown .dropdown-toggle { - color: inherit; -} -.panel-title { - margin-top: 0; - margin-bottom: 0; - font-size: 16px; - color: inherit; -} -.panel-title > a, -.panel-title > small, -.panel-title > .small, -.panel-title > small > a, -.panel-title > .small > a { - color: inherit; -} -.panel-footer { - padding: 10px 15px; - background-color: #f5f5f5; - border-top: 1px solid #ddd; - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} -.panel > .list-group, -.panel > .panel-collapse > .list-group { - margin-bottom: 0; -} -.panel > .list-group .list-group-item, -.panel > .panel-collapse > .list-group .list-group-item { - border-width: 1px 0; - border-radius: 0; -} -.panel > .list-group:first-child .list-group-item:first-child, -.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { - border-top: 0; - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} -.panel > .list-group:last-child .list-group-item:last-child, -.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { - border-bottom: 0; - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} -.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child { - border-top-left-radius: 0; - border-top-right-radius: 0; -} -.panel-heading + .list-group .list-group-item:first-child { - border-top-width: 0; -} -.list-group + .panel-footer { - border-top-width: 0; -} -.panel > .table, -.panel > .table-responsive > .table, -.panel > .panel-collapse > .table { - margin-bottom: 0; -} -.panel > .table caption, -.panel > .table-responsive > .table caption, -.panel > .panel-collapse > .table caption { - padding-right: 15px; - padding-left: 15px; -} -.panel > .table:first-child, -.panel > .table-responsive:first-child > .table:first-child { - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} -.panel > .table:first-child > thead:first-child > tr:first-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, -.panel > .table:first-child > tbody:first-child > tr:first-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} -.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, -.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, -.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, -.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { - border-top-left-radius: 3px; -} -.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, -.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, -.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, -.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { - border-top-right-radius: 3px; -} -.panel > .table:last-child, -.panel > .table-responsive:last-child > .table:last-child { - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} -.panel > .table:last-child > tbody:last-child > tr:last-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} -.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, -.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { - border-bottom-left-radius: 3px; -} -.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, -.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { - border-bottom-right-radius: 3px; -} -.panel > .panel-body + .table, -.panel > .panel-body + .table-responsive, -.panel > .table + .panel-body, -.panel > .table-responsive + .panel-body { - border-top: 1px solid #ddd; -} -.panel > .table > tbody:first-child > tr:first-child th, -.panel > .table > tbody:first-child > tr:first-child td { - border-top: 0; -} -.panel > .table-bordered, -.panel > .table-responsive > .table-bordered { - border: 0; -} -.panel > .table-bordered > thead > tr > th:first-child, -.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, -.panel > .table-bordered > tbody > tr > th:first-child, -.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, -.panel > .table-bordered > tfoot > tr > th:first-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, -.panel > .table-bordered > thead > tr > td:first-child, -.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, -.panel > .table-bordered > tbody > tr > td:first-child, -.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, -.panel > .table-bordered > tfoot > tr > td:first-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { - border-left: 0; -} -.panel > .table-bordered > thead > tr > th:last-child, -.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, -.panel > .table-bordered > tbody > tr > th:last-child, -.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, -.panel > .table-bordered > tfoot > tr > th:last-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, -.panel > .table-bordered > thead > tr > td:last-child, -.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, -.panel > .table-bordered > tbody > tr > td:last-child, -.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, -.panel > .table-bordered > tfoot > tr > td:last-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { - border-right: 0; -} -.panel > .table-bordered > thead > tr:first-child > td, -.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, -.panel > .table-bordered > tbody > tr:first-child > td, -.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, -.panel > .table-bordered > thead > tr:first-child > th, -.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, -.panel > .table-bordered > tbody > tr:first-child > th, -.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { - border-bottom: 0; -} -.panel > .table-bordered > tbody > tr:last-child > td, -.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, -.panel > .table-bordered > tfoot > tr:last-child > td, -.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, -.panel > .table-bordered > tbody > tr:last-child > th, -.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, -.panel > .table-bordered > tfoot > tr:last-child > th, -.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { - border-bottom: 0; -} -.panel > .table-responsive { - margin-bottom: 0; - border: 0; -} -.panel-group { - margin-bottom: 20px; -} -.panel-group .panel { - margin-bottom: 0; - border-radius: 4px; -} -.panel-group .panel + .panel { - margin-top: 5px; -} -.panel-group .panel-heading { - border-bottom: 0; -} -.panel-group .panel-heading + .panel-collapse > .panel-body, -.panel-group .panel-heading + .panel-collapse > .list-group { - border-top: 1px solid #ddd; -} -.panel-group .panel-footer { - border-top: 0; -} -.panel-group .panel-footer + .panel-collapse .panel-body { - border-bottom: 1px solid #ddd; -} -.panel-default { - border-color: #ddd; -} -.panel-default > .panel-heading { - color: #333; - background-color: #f5f5f5; - border-color: #ddd; -} -.panel-default > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #ddd; -} -.panel-default > .panel-heading .badge { - color: #f5f5f5; - background-color: #333; -} -.panel-default > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #ddd; -} -.panel-primary { - border-color: #337ab7; -} -.panel-primary > .panel-heading { - color: #fff; - background-color: #337ab7; - border-color: #337ab7; -} -.panel-primary > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #337ab7; -} -.panel-primary > .panel-heading .badge { - color: #337ab7; - background-color: #fff; -} -.panel-primary > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #337ab7; -} -.panel-success { - border-color: #d6e9c6; -} -.panel-success > .panel-heading { - color: #3c763d; - background-color: #dff0d8; - border-color: #d6e9c6; -} -.panel-success > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #d6e9c6; -} -.panel-success > .panel-heading .badge { - color: #dff0d8; - background-color: #3c763d; -} -.panel-success > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #d6e9c6; -} -.panel-info { - border-color: #bce8f1; -} -.panel-info > .panel-heading { - color: #31708f; - background-color: #d9edf7; - border-color: #bce8f1; -} -.panel-info > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #bce8f1; -} -.panel-info > .panel-heading .badge { - color: #d9edf7; - background-color: #31708f; -} -.panel-info > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #bce8f1; -} -.panel-warning { - border-color: #faebcc; -} -.panel-warning > .panel-heading { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #faebcc; -} -.panel-warning > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #faebcc; -} -.panel-warning > .panel-heading .badge { - color: #fcf8e3; - background-color: #8a6d3b; -} -.panel-warning > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #faebcc; -} -.panel-danger { - border-color: #ebccd1; -} -.panel-danger > .panel-heading { - color: #a94442; - background-color: #f2dede; - border-color: #ebccd1; -} -.panel-danger > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #ebccd1; -} -.panel-danger > .panel-heading .badge { - color: #f2dede; - background-color: #a94442; -} -.panel-danger > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #ebccd1; -} -.embed-responsive { - position: relative; - display: block; - height: 0; - padding: 0; - overflow: hidden; -} -.embed-responsive .embed-responsive-item, -.embed-responsive iframe, -.embed-responsive embed, -.embed-responsive object, -.embed-responsive video { - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - border: 0; -} -.embed-responsive-16by9 { - padding-bottom: 56.25%; -} -.embed-responsive-4by3 { - padding-bottom: 75%; -} -.well { - min-height: 20px; - padding: 19px; - margin-bottom: 20px; - background-color: #f5f5f5; - border: 1px solid #e3e3e3; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); -} -.well blockquote { - border-color: #ddd; - border-color: rgba(0, 0, 0, .15); -} -.well-lg { - padding: 24px; - border-radius: 6px; -} -.well-sm { - padding: 9px; - border-radius: 3px; -} -.close { - float: right; - font-size: 21px; - font-weight: bold; - line-height: 1; - color: #000; - text-shadow: 0 1px 0 #fff; - filter: alpha(opacity=20); - opacity: .2; -} -.close:hover, -.close:focus { - color: #000; - text-decoration: none; - cursor: pointer; - filter: alpha(opacity=50); - opacity: .5; -} -button.close { - -webkit-appearance: none; - padding: 0; - cursor: pointer; - background: transparent; - border: 0; -} -.modal-open { - overflow: hidden; -} -.modal { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1050; - display: none; - overflow: hidden; - -webkit-overflow-scrolling: touch; - outline: 0; -} -.modal.fade .modal-dialog { - -webkit-transition: -webkit-transform .3s ease-out; - -o-transition: -o-transform .3s ease-out; - transition: transform .3s ease-out; - -webkit-transform: translate(0, -25%); - -ms-transform: translate(0, -25%); - -o-transform: translate(0, -25%); - transform: translate(0, -25%); -} -.modal.in .modal-dialog { - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - -o-transform: translate(0, 0); - transform: translate(0, 0); -} -.modal-open .modal { - overflow-x: hidden; - overflow-y: auto; -} -.modal-dialog { - position: relative; - width: auto; - margin: 10px; -} -.modal-content { - position: relative; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #999; - border: 1px solid rgba(0, 0, 0, .2); - border-radius: 6px; - outline: 0; - -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); - box-shadow: 0 3px 9px rgba(0, 0, 0, .5); -} -.modal-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1040; - background-color: #000; -} -.modal-backdrop.fade { - filter: alpha(opacity=0); - opacity: 0; -} -.modal-backdrop.in { - filter: alpha(opacity=50); - opacity: .5; -} -.modal-header { - padding: 15px; - border-bottom: 1px solid #e5e5e5; -} -.modal-header .close { - margin-top: -2px; -} -.modal-title { - margin: 0; - line-height: 1.42857143; -} -.modal-body { - position: relative; - padding: 15px; -} -.modal-footer { - padding: 15px; - text-align: right; - border-top: 1px solid #e5e5e5; -} -.modal-footer .btn + .btn { - margin-bottom: 0; - margin-left: 5px; -} -.modal-footer .btn-group .btn + .btn { - margin-left: -1px; -} -.modal-footer .btn-block + .btn-block { - margin-left: 0; -} -.modal-scrollbar-measure { - position: absolute; - top: -9999px; - width: 50px; - height: 50px; - overflow: scroll; -} -@media (min-width: 768px) { - .modal-dialog { - width: 600px; - margin: 30px auto; - } - .modal-content { - -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5); - box-shadow: 0 5px 15px rgba(0, 0, 0, .5); - } - .modal-sm { - width: 300px; - } -} -@media (min-width: 992px) { - .modal-lg { - width: 900px; - } -} -.tooltip { - position: absolute; - z-index: 1070; - display: block; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 12px; - font-style: normal; - font-weight: normal; - line-height: 1.42857143; - text-align: left; - text-align: start; - text-decoration: none; - text-shadow: none; - text-transform: none; - letter-spacing: normal; - word-break: normal; - word-spacing: normal; - word-wrap: normal; - white-space: normal; - filter: alpha(opacity=0); - opacity: 0; - - line-break: auto; -} -.tooltip.in { - filter: alpha(opacity=90); - opacity: .9; -} -.tooltip.top { - padding: 5px 0; - margin-top: -3px; -} -.tooltip.right { - padding: 0 5px; - margin-left: 3px; -} -.tooltip.bottom { - padding: 5px 0; - margin-top: 3px; -} -.tooltip.left { - padding: 0 5px; - margin-left: -3px; -} -.tooltip-inner { - max-width: 200px; - padding: 3px 8px; - color: #fff; - text-align: center; - background-color: #000; - border-radius: 4px; -} -.tooltip-arrow { - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} -.tooltip.top .tooltip-arrow { - bottom: 0; - left: 50%; - margin-left: -5px; - border-width: 5px 5px 0; - border-top-color: #000; -} -.tooltip.top-left .tooltip-arrow { - right: 5px; - bottom: 0; - margin-bottom: -5px; - border-width: 5px 5px 0; - border-top-color: #000; -} -.tooltip.top-right .tooltip-arrow { - bottom: 0; - left: 5px; - margin-bottom: -5px; - border-width: 5px 5px 0; - border-top-color: #000; -} -.tooltip.right .tooltip-arrow { - top: 50%; - left: 0; - margin-top: -5px; - border-width: 5px 5px 5px 0; - border-right-color: #000; -} -.tooltip.left .tooltip-arrow { - top: 50%; - right: 0; - margin-top: -5px; - border-width: 5px 0 5px 5px; - border-left-color: #000; -} -.tooltip.bottom .tooltip-arrow { - top: 0; - left: 50%; - margin-left: -5px; - border-width: 0 5px 5px; - border-bottom-color: #000; -} -.tooltip.bottom-left .tooltip-arrow { - top: 0; - right: 5px; - margin-top: -5px; - border-width: 0 5px 5px; - border-bottom-color: #000; -} -.tooltip.bottom-right .tooltip-arrow { - top: 0; - left: 5px; - margin-top: -5px; - border-width: 0 5px 5px; - border-bottom-color: #000; -} -.popover { - position: absolute; - top: 0; - left: 0; - z-index: 1060; - display: none; - max-width: 276px; - padding: 1px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - font-style: normal; - font-weight: normal; - line-height: 1.42857143; - text-align: left; - text-align: start; - text-decoration: none; - text-shadow: none; - text-transform: none; - letter-spacing: normal; - word-break: normal; - word-spacing: normal; - word-wrap: normal; - white-space: normal; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, .2); - border-radius: 6px; - -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); - box-shadow: 0 5px 10px rgba(0, 0, 0, .2); - - line-break: auto; -} -.popover.top { - margin-top: -10px; -} -.popover.right { - margin-left: 10px; -} -.popover.bottom { - margin-top: 10px; -} -.popover.left { - margin-left: -10px; -} -.popover-title { - padding: 8px 14px; - margin: 0; - font-size: 14px; - background-color: #f7f7f7; - border-bottom: 1px solid #ebebeb; - border-radius: 5px 5px 0 0; -} -.popover-content { - padding: 9px 14px; -} -.popover > .arrow, -.popover > .arrow:after { - position: absolute; - display: block; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} -.popover > .arrow { - border-width: 11px; -} -.popover > .arrow:after { - content: ""; - border-width: 10px; -} -.popover.top > .arrow { - bottom: -11px; - left: 50%; - margin-left: -11px; - border-top-color: #999; - border-top-color: rgba(0, 0, 0, .25); - border-bottom-width: 0; -} -.popover.top > .arrow:after { - bottom: 1px; - margin-left: -10px; - content: " "; - border-top-color: #fff; - border-bottom-width: 0; -} -.popover.right > .arrow { - top: 50%; - left: -11px; - margin-top: -11px; - border-right-color: #999; - border-right-color: rgba(0, 0, 0, .25); - border-left-width: 0; -} -.popover.right > .arrow:after { - bottom: -10px; - left: 1px; - content: " "; - border-right-color: #fff; - border-left-width: 0; -} -.popover.bottom > .arrow { - top: -11px; - left: 50%; - margin-left: -11px; - border-top-width: 0; - border-bottom-color: #999; - border-bottom-color: rgba(0, 0, 0, .25); -} -.popover.bottom > .arrow:after { - top: 1px; - margin-left: -10px; - content: " "; - border-top-width: 0; - border-bottom-color: #fff; -} -.popover.left > .arrow { - top: 50%; - right: -11px; - margin-top: -11px; - border-right-width: 0; - border-left-color: #999; - border-left-color: rgba(0, 0, 0, .25); -} -.popover.left > .arrow:after { - right: 1px; - bottom: -10px; - content: " "; - border-right-width: 0; - border-left-color: #fff; -} -.carousel { - position: relative; -} -.carousel-inner { - position: relative; - width: 100%; - overflow: hidden; -} -.carousel-inner > .item { - position: relative; - display: none; - -webkit-transition: .6s ease-in-out left; - -o-transition: .6s ease-in-out left; - transition: .6s ease-in-out left; -} -.carousel-inner > .item > img, -.carousel-inner > .item > a > img { - line-height: 1; -} -@media all and (transform-3d), (-webkit-transform-3d) { - .carousel-inner > .item { - -webkit-transition: -webkit-transform .6s ease-in-out; - -o-transition: -o-transform .6s ease-in-out; - transition: transform .6s ease-in-out; - - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - -webkit-perspective: 1000px; - perspective: 1000px; - } - .carousel-inner > .item.next, - .carousel-inner > .item.active.right { - left: 0; - -webkit-transform: translate3d(100%, 0, 0); - transform: translate3d(100%, 0, 0); - } - .carousel-inner > .item.prev, - .carousel-inner > .item.active.left { - left: 0; - -webkit-transform: translate3d(-100%, 0, 0); - transform: translate3d(-100%, 0, 0); - } - .carousel-inner > .item.next.left, - .carousel-inner > .item.prev.right, - .carousel-inner > .item.active { - left: 0; - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } -} -.carousel-inner > .active, -.carousel-inner > .next, -.carousel-inner > .prev { - display: block; -} -.carousel-inner > .active { - left: 0; -} -.carousel-inner > .next, -.carousel-inner > .prev { - position: absolute; - top: 0; - width: 100%; -} -.carousel-inner > .next { - left: 100%; -} -.carousel-inner > .prev { - left: -100%; -} -.carousel-inner > .next.left, -.carousel-inner > .prev.right { - left: 0; -} -.carousel-inner > .active.left { - left: -100%; -} -.carousel-inner > .active.right { - left: 100%; -} -.carousel-control { - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: 15%; - font-size: 20px; - color: #fff; - text-align: center; - text-shadow: 0 1px 2px rgba(0, 0, 0, .6); - background-color: rgba(0, 0, 0, 0); - filter: alpha(opacity=50); - opacity: .5; -} -.carousel-control.left { - background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); - background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); - background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001))); - background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); - background-repeat: repeat-x; -} -.carousel-control.right { - right: 0; - left: auto; - background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); - background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); - background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5))); - background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); - background-repeat: repeat-x; -} -.carousel-control:hover, -.carousel-control:focus { - color: #fff; - text-decoration: none; - filter: alpha(opacity=90); - outline: 0; - opacity: .9; -} -.carousel-control .icon-prev, -.carousel-control .icon-next, -.carousel-control .glyphicon-chevron-left, -.carousel-control .glyphicon-chevron-right { - position: absolute; - top: 50%; - z-index: 5; - display: inline-block; - margin-top: -10px; -} -.carousel-control .icon-prev, -.carousel-control .glyphicon-chevron-left { - left: 50%; - margin-left: -10px; -} -.carousel-control .icon-next, -.carousel-control .glyphicon-chevron-right { - right: 50%; - margin-right: -10px; -} -.carousel-control .icon-prev, -.carousel-control .icon-next { - width: 20px; - height: 20px; - font-family: serif; - line-height: 1; -} -.carousel-control .icon-prev:before { - content: '\2039'; -} -.carousel-control .icon-next:before { - content: '\203a'; -} -.carousel-indicators { - position: absolute; - bottom: 10px; - left: 50%; - z-index: 15; - width: 60%; - padding-left: 0; - margin-left: -30%; - text-align: center; - list-style: none; -} -.carousel-indicators li { - display: inline-block; - width: 10px; - height: 10px; - margin: 1px; - text-indent: -999px; - cursor: pointer; - background-color: #000 \9; - background-color: rgba(0, 0, 0, 0); - border: 1px solid #fff; - border-radius: 10px; -} -.carousel-indicators .active { - width: 12px; - height: 12px; - margin: 0; - background-color: #fff; -} -.carousel-caption { - position: absolute; - right: 15%; - bottom: 20px; - left: 15%; - z-index: 10; - padding-top: 20px; - padding-bottom: 20px; - color: #fff; - text-align: center; - text-shadow: 0 1px 2px rgba(0, 0, 0, .6); -} -.carousel-caption .btn { - text-shadow: none; -} -@media screen and (min-width: 768px) { - .carousel-control .glyphicon-chevron-left, - .carousel-control .glyphicon-chevron-right, - .carousel-control .icon-prev, - .carousel-control .icon-next { - width: 30px; - height: 30px; - margin-top: -10px; - font-size: 30px; - } - .carousel-control .glyphicon-chevron-left, - .carousel-control .icon-prev { - margin-left: -10px; - } - .carousel-control .glyphicon-chevron-right, - .carousel-control .icon-next { - margin-right: -10px; - } - .carousel-caption { - right: 20%; - left: 20%; - padding-bottom: 30px; - } - .carousel-indicators { - bottom: 20px; - } -} -.clearfix:before, -.clearfix:after, -.dl-horizontal dd:before, -.dl-horizontal dd:after, -.container:before, -.container:after, -.container-fluid:before, -.container-fluid:after, -.row:before, -.row:after, -.form-horizontal .form-group:before, -.form-horizontal .form-group:after, -.btn-toolbar:before, -.btn-toolbar:after, -.btn-group-vertical > .btn-group:before, -.btn-group-vertical > .btn-group:after, -.nav:before, -.nav:after, -.navbar:before, -.navbar:after, -.navbar-header:before, -.navbar-header:after, -.navbar-collapse:before, -.navbar-collapse:after, -.pager:before, -.pager:after, -.panel-body:before, -.panel-body:after, -.modal-header:before, -.modal-header:after, -.modal-footer:before, -.modal-footer:after { - display: table; - content: " "; -} -.clearfix:after, -.dl-horizontal dd:after, -.container:after, -.container-fluid:after, -.row:after, -.form-horizontal .form-group:after, -.btn-toolbar:after, -.btn-group-vertical > .btn-group:after, -.nav:after, -.navbar:after, -.navbar-header:after, -.navbar-collapse:after, -.pager:after, -.panel-body:after, -.modal-header:after, -.modal-footer:after { - clear: both; -} -.center-block { - display: block; - margin-right: auto; - margin-left: auto; -} -.pull-right { - float: right !important; -} -.pull-left { - float: left !important; -} -.hide { - display: none !important; -} -.show { - display: block !important; -} -.invisible { - visibility: hidden; -} -.text-hide { - font: 0/0 a; - color: transparent; - text-shadow: none; - background-color: transparent; - border: 0; -} -.hidden { - display: none !important; -} -.affix { - position: fixed; -} -@-ms-viewport { - width: device-width; -} -.visible-xs, -.visible-sm, -.visible-md, -.visible-lg { - display: none !important; -} -.visible-xs-block, -.visible-xs-inline, -.visible-xs-inline-block, -.visible-sm-block, -.visible-sm-inline, -.visible-sm-inline-block, -.visible-md-block, -.visible-md-inline, -.visible-md-inline-block, -.visible-lg-block, -.visible-lg-inline, -.visible-lg-inline-block { - display: none !important; -} -@media (max-width: 767px) { - .visible-xs { - display: block !important; - } - table.visible-xs { - display: table !important; - } - tr.visible-xs { - display: table-row !important; - } - th.visible-xs, - td.visible-xs { - display: table-cell !important; - } -} -@media (max-width: 767px) { - .visible-xs-block { - display: block !important; - } -} -@media (max-width: 767px) { - .visible-xs-inline { - display: inline !important; - } -} -@media (max-width: 767px) { - .visible-xs-inline-block { - display: inline-block !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm { - display: block !important; - } - table.visible-sm { - display: table !important; - } - tr.visible-sm { - display: table-row !important; - } - th.visible-sm, - td.visible-sm { - display: table-cell !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm-block { - display: block !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm-inline { - display: inline !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm-inline-block { - display: inline-block !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md { - display: block !important; - } - table.visible-md { - display: table !important; - } - tr.visible-md { - display: table-row !important; - } - th.visible-md, - td.visible-md { - display: table-cell !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md-block { - display: block !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md-inline { - display: inline !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md-inline-block { - display: inline-block !important; - } -} -@media (min-width: 1200px) { - .visible-lg { - display: block !important; - } - table.visible-lg { - display: table !important; - } - tr.visible-lg { - display: table-row !important; - } - th.visible-lg, - td.visible-lg { - display: table-cell !important; - } -} -@media (min-width: 1200px) { - .visible-lg-block { - display: block !important; - } -} -@media (min-width: 1200px) { - .visible-lg-inline { - display: inline !important; - } -} -@media (min-width: 1200px) { - .visible-lg-inline-block { - display: inline-block !important; - } -} -@media (max-width: 767px) { - .hidden-xs { - display: none !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .hidden-sm { - display: none !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .hidden-md { - display: none !important; - } -} -@media (min-width: 1200px) { - .hidden-lg { - display: none !important; - } -} -.visible-print { - display: none !important; -} -@media print { - .visible-print { - display: block !important; - } - table.visible-print { - display: table !important; - } - tr.visible-print { - display: table-row !important; - } - th.visible-print, - td.visible-print { - display: table-cell !important; - } -} -.visible-print-block { - display: none !important; -} -@media print { - .visible-print-block { - display: block !important; - } -} -.visible-print-inline { - display: none !important; -} -@media print { - .visible-print-inline { - display: inline !important; - } -} -.visible-print-inline-block { - display: none !important; -} -@media print { - .visible-print-inline-block { - display: inline-block !important; - } -} -@media print { - .hidden-print { - display: none !important; - } -} -/*# sourceMappingURL=bootstrap.css.map */ diff --git a/wiresx/finish.php b/wiresx/finish.php deleted file mode 100644 index 261cdc7..0000000 --- a/wiresx/finish.php +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - Set Frequency - - - - - -
-

Hot-Spot Frequencies Summary

-

Hot-spot Callsign:

-

Transmit Frequency: MHz

-

Receive Frequency: MHz

-

It can take up to 15 minutes for the frequency data to be available to the reflector. Please be patient.

-
-

- Reset Frequencies - Reset Your Password - Sign Out of Your Account -

- - diff --git a/wiresx/frequency.php b/wiresx/frequency.php deleted file mode 100644 index d89a42d..0000000 --- a/wiresx/frequency.php +++ /dev/null @@ -1,126 +0,0 @@ - 1000.0 || $txfreq < 10.0) { - $txfreq_err = "TX out of range."; - } - // Validate confirm password - if ($rxfreq > 1000.0 || $rxfreq < 10.0) { - $rxfreq_err = "RX out of range."; - } - - // Check input errors before updating the database - if (empty($txfreq_err) && empty($rxfreq_err)) { - // Prepare an update statement - $sql = "UPDATE ysfnodes SET txfreq = ?, rxfreq = ? WHERE callsign = ?"; - - if($stmt = mysqli_prepare($link, $sql)){ - // Bind variables to the prepared statement as parameters - $tx_freq_hz = $_POST["txfreq"] * 1000000; - $rx_freq_hz = $_POST["rxfreq"] * 1000000; - mysqli_stmt_bind_param($stmt, "iis", $tx_freq_hz, $rx_freq_hz, $_SESSION["callsign"]); - - // Attempt to execute the prepared statement - if(mysqli_stmt_execute($stmt)){ - // Frequencies updated successfully, go to summary - header("location: finish.php"); - exit(); - } else{ - echo "Oops! Something went wrong. Please try again later."; - } - - // Close statement - mysqli_stmt_close($stmt); - } - } - - // Close connection - mysqli_close($link); -} -?> - - - - - - Set Frequency - - - - - -
-

Hot-Spot Frequencies

-

Set your hot-spot frequencies (in MHz) here.

-
" method="post"> -
- - - -
-
- - - -
-
- - Cancel -
-
-
-

- Reset Your Password - Sign Out of Your Account -

- - diff --git a/wiresx/login.php b/wiresx/login.php deleted file mode 100644 index ba7038a..0000000 --- a/wiresx/login.php +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - Login - - - - -
-

Login

-

Login with your hot-spot callsign.

-
" method="post"> -
- - - -
-
- - - -
-
- -
-

Don't have an account? Sign up now.

-
-
- - diff --git a/wiresx/logout.php b/wiresx/logout.php deleted file mode 100644 index 8fc70a3..0000000 --- a/wiresx/logout.php +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/wiresx/register.php b/wiresx/register.php deleted file mode 100644 index b92bba5..0000000 --- a/wiresx/register.php +++ /dev/null @@ -1,148 +0,0 @@ - 7) { - $callsign_err = "Callsign is too long."; - } else if (! IsValidCallsign(strtoupper(trim($_POST["callsign"])))) { - $callsign_err = "Not a valid callsign."; - } else { - // Prepare a select statement - $sql = "SELECT * FROM ysfnodes WHERE callsign = ?"; - - if ($stmt = mysqli_prepare($link, $sql)) { - // Bind variables to the prepared statement as parameters - mysqli_stmt_bind_param($stmt, "s", $param_callsign); - - // Set parameters - $param_callsign = strtoupper(trim($_POST["callsign"])); - - // Attempt to execute the prepared statement - if (mysqli_stmt_execute($stmt)) { - /* store result */ - mysqli_stmt_store_result($stmt); - - if (mysqli_stmt_num_rows($stmt) == 1) { - $callsign_err = "This callsign is already taken."; - } else { - $callsign = strtoupper(trim($_POST["callsign"])); - } - } else { - echo "Oops! Something went wrong. Please try again later."; - } - - // Close statement - mysqli_stmt_close($stmt); - } - } - - // Validate password - if (empty(trim($_POST["password"]))) { - $password_err = "Please enter a password."; - } elseif (strlen(trim($_POST["password"])) < 6) { - $password_err = "Password must have atleast 6 characters."; - } else { - $password = trim($_POST["password"]); - } - - // Validate confirm password - if (empty(trim($_POST["confirm_password"]))) { - $confirm_password_err = "Please confirm password."; - } else { - $confirm_password = trim($_POST["confirm_password"]); - if(empty($password_err) && ($password != $confirm_password)){ - $confirm_password_err = "Password did not match."; - } - } - - // Check input errors before inserting in database - if (empty($callsign_err) && empty($password_err) && empty($confirm_password_err)) { - - // Prepare an insert statement - $sql = "INSERT INTO ysfnodes (callsign, password) VALUES (?, ?)"; - - if ($stmt = mysqli_prepare($link, $sql)) { - // Bind variables to the prepared statement as parameters - mysqli_stmt_bind_param($stmt, "ss", $param_callsign, $param_password); - - // Set parameters - $param_callsign = $callsign; - $param_password = password_hash($password, PASSWORD_DEFAULT); // Creates a password hash - - // Attempt to execute the prepared statement - if (mysqli_stmt_execute($stmt)) { - // Redirect to login page - header("location: login.php"); - } else { - echo "Something went wrong. Please try again later."; - } - - // Close statement - mysqli_stmt_close($stmt); - } - } - - // Close connection - mysqli_close($link); -} -?> - - - - - - Hot-Spot Frequency for WiresX Registration - - - - -
-

Sign Up

-

Please fill this form to create an account. Use your hot-spot callsign to register.

-
" method="post"> -
- - - -
-
- - - -
-
- - - -
-
- - -
-

Already have an account? Login here.

-
-
- - diff --git a/wiresx/reset-password.php b/wiresx/reset-password.php deleted file mode 100644 index 94f5569..0000000 --- a/wiresx/reset-password.php +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - Reset Password - - - - -
-

Reset Password

-

Please fill out this form to reset your password.

-
" method="post"> -
- - - -
-
- - - -
-
- - Cancel -
-
-
- -