#!/usr/bin/bash
#
# HENCSAT QoS Control
# Copyright (C) 2017-2026 by Thomas Dreibholz
#
# 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 <http://www.gnu.org/licenses/>.
#
# Contact: dreibh@simula.no

# Bash options:
set -eu


# Ensure running as root
if [ "$(id -u)" != "0" ]; then
   exec sudo "$0" "$@"
fi

CONFIG_FILE="/etc/hencsat/hencsat-router.conf"
LASTSETTINGS_FILE="/etc/hencsat/hencsat-router.last"


# ###### Write log information ##############################################
log ()
{
   echo -e "\x1b[1;34m$(date +%FT%H:%M:%S): $*\x1b[0m"
}


# ###### Write log action information #######################################
log-action ()
{
   echo -en "\x1b[34m$(date +%FT%H:%M:%S): $*\x1b[0m"
}


# ###### Write log result information #######################################
RESULT_GOOD=1
RESULT_BAD=0
_BadResults=0
log-result ()
{
   local result="$1"
   local line=120

   if [ "${result}" = "$RESULT_GOOD" ] ; then
      echo -en "\x1b[${line}G\x1b[32mOK\x1b[0m\n"
   elif [ "${result}" = "$RESULT_BAD" ] ; then
      echo -en "\x1b[${line}G\x1b[30;47;5mFAILED!\x1b[0m\n"
      _BadResults=$((_BadResults + 1))
   elif [ "${result}" = "" ] ; then
      echo >&2 "ERROR: No result given for log-result?!"
      exit 1
   else
      echo -en "\x1b[${line}G\x1b[0m${result}\n"
   fi
}


# ###### Stop QoS on interface ##############################################
qos-control-stop ()
{
   local interface="$1"
   local baseID="$2"

   local htbID=${baseID}
   local netemID=$((baseID + 1))

   log-action "Stopping QoS on ${interface} ..."
   tc qdisc del dev "${interface}" root handle "${htbID}": 2>/dev/null || true
   log-result $RESULT_GOOD

   # tc qdisc ls dev ${interface}
}


# ###### Get QoS statistics of interface ####################################
qos-control-statistics()
{
   local interface="$1"
   local baseID="$2"

   local htbID=${baseID}
   local netemID=$((baseID + 1))

   env LANG=C tc -s qdisc show dev "${interface}" | \
      grep -A1 "^qdisc netem ${netemID}:" | \
      grep "^ Sent" | \
      awk "{ printf \"${interface}\t%-10s\t%-10s\n\", \$4, \$2 }"
}


# ###### Modify QoS parameters on interface #################################
qos-control-change()
{
   local interface="$1"
   local baseID="$2"      # Use *even* number!
   local bandwidth="$3"   # in Kbit/s
   local delay="$4"       # in ms
   local loss="$5"        # in %
   local error="$6"       # in %

   local htbID=${baseID}
   local netemID=$((baseID + 1))

   log-action "Modify ${interface}: ${bandwidth} Kbit/s, ${delay} ms, ${loss}% loss, ${error}% error"

   # shellcheck disable=SC2015
   tc class change dev "${interface}" parent "${htbID}":0 classid "${htbID}":1 htb rate "${bandwidth}"kbit burst 10000000 && \
   tc qdisc change dev "${interface}" parent "${htbID}":1 handle ${netemID}: netem delay "${delay}"ms loss "${loss}"% corrupt "${error}"% limit 100000 && \
   log-result $RESULT_GOOD || log-result $RESULT_BAD

   tc qdisc ls dev "${interface}"
}


# ###### Start QoS on interface #############################################
qos-control-start ()
{
   local interface="$1"
   local baseID="$2"      # Use *even* number!
   local bandwidth="$3"   # in Kbit/s
   local delay="$4"       # in ms
   local loss="$5"        # in %
   local error="$6"       # in %

   local htbID=${baseID}
   local netemID=$((baseID + 1))

   log-action "Starting QoS on ${interface} ..."
   tc qdisc del dev "${interface}" root handle "${htbID}": 2>/dev/null || true

   # shellcheck disable=SC2015
   tc qdisc add dev "${interface}" root handle "${htbID}": htb default 1 && \
   tc class add dev "${interface}" parent "${htbID}":0 classid "${htbID}":1 htb rate "${bandwidth}"kbit && \
   tc qdisc add dev "${interface}" parent "${htbID}":1 handle ${netemID}: netem && \
   log-result $RESULT_GOOD || log-result $RESULT_BAD

   qos-control-change "${interface}" "${baseID}" "${bandwidth}" "${delay}" "${loss}" "${error}"
}



# ###### Main program #######################################################

# ====== Configuration checks ===============================================
if [ $# -lt 1 ] ; then
   echo >&2 "Usage: $0 [[start|change] left_bandwidth_kbit_per_s left_delay_ms left_loss_rate_% left_error_rate_% [right_bandwidth_kbit_per_s right_delay_ms right_loss_rate_% right_error_rate_% [id]]|stats|info|stop]"
   exit 1
fi
if [ ! -e "${CONFIG_FILE}" ] ; then
   echo >&2 "ERROR: Configuration file ${CONFIG_FILE} not found!"
   exit 1
fi

. ${CONFIG_FILE}


# ====== Start or Change ====================================================
action="$1"
if [ "${action}" == "start" ] || [ "${action}" == "change" ] ; then
   if [ $# -eq 5 ] || [ $# -ge 9 ] ; then
      leftBandwidth="$2"    # in Kbit/s
      leftDelay="$3"        # in ms
      leftLoss="$4"         # in %
      leftError="$5"        # in %
      if [ $# -le 5 ] ; then
         rightBandwidth="${leftBandwidth}"
         rightDelay="${leftDelay}"
         rightLoss="${leftLoss}"
         rightError="${leftError}"
      else
         rightBandwidth="$6"   # in Kbit/s
         rightDelay="$7"       # in ms
         rightLoss="$8"        # in %
         rightError="$9"       # in %
      fi
      if [ $# -ge 10 ] ; then
         id="${10}"            # String (regexp: [0-9a-Z\.-]+)
      else
         id=""
      fi
      if [ $# -ge 11 ] ; then
         option="${11}"        # -machinereadable
      else
         option=""
      fi
      if [ "${id}" == "" ] ; then
         id="$(uuid)"
      fi
      if [ "${action}" == "start" ] ; then
         qos-control-start "${ROUTER_INTERFACE_LEFT}"   10  "${leftBandwidth}"  "${leftDelay}"  "${leftLoss}"  "${leftError}"
         qos-control-start "${ROUTER_INTERFACE_RIGHT}"  20  "${rightBandwidth}" "${rightDelay}" "${rightLoss}" "${rightError}"
      elif [ "${action}" == "change" ] ; then
         qos-control-change "${ROUTER_INTERFACE_LEFT}"   10  "${leftBandwidth}"  "${leftDelay}"  "${leftLoss}"  "${leftError}"
         qos-control-change "${ROUTER_INTERFACE_RIGHT}"  20  "${rightBandwidth}" "${rightDelay}" "${rightLoss}" "${rightError}"
      fi

      echo -e "${leftBandwidth}\t${leftDelay}\t${leftLoss}\t${leftError}\t${rightBandwidth}\t${rightDelay}\t${rightLoss}\t${rightError}\t${id}" | tee ${LASTSETTINGS_FILE} >/dev/null

      if [ "${action}" == "change" ] ; then
         if [ "${option}" == "-machinereadable" ] ; then
            echo -e "CHANGE\t$(cat ${LASTSETTINGS_FILE})"
         fi
      fi
   else
      echo >&2 "ERROR: Not enough parameters for \"${action}\" given!"
      exit 1
   fi

# ====== Stop ===============================================================
elif [ "${action}" = "stop" ] ; then
   rm -f ${LASTSETTINGS_FILE}
   qos-control-stop "${ROUTER_INTERFACE_LEFT}"  10
   qos-control-stop "${ROUTER_INTERFACE_RIGHT}" 20

# ====== Info ===============================================================
elif [ "${action}" = "info" ] ; then
   if [ -e ${LASTSETTINGS_FILE} ] ; then
      echo -e "INFO\t$(cat ${LASTSETTINGS_FILE})"
   else
      echo -e "INFO\t-1\t-1\t-1\t-1\t-1\t-1\t-1\t-1\t0"
   fi

# ====== Statistics =========================================================
elif [ "${action}" = "stats" ] ; then
   left=$(qos-control-statistics "${ROUTER_INTERFACE_LEFT}" 10)
   right=$(qos-control-statistics "${ROUTER_INTERFACE_RIGHT}" 20)
   now=$(date +%FT%H:%M:%S.%N)
   if [ $# -ge 2 ] && [ "$2" = "-machinereadable" ] ; then
      echo -e "STATS\t${now}\tLEFT\t${left}\tRIGHT\t${right}"
      if [ -e ${LASTSETTINGS_FILE} ] ; then
         echo -e "INFO\t$(cat ${LASTSETTINGS_FILE})"
      fi
   else
      log "Statistics:"
      echo -e "time: \t${now}"
      echo -e "left: \t${left}"
      echo -e "right:\t${right}"
   fi

# ====== Error ==============================================================
else
   echo >&2 "ERROR: Unknown command \"${action}\"!"
   exit 1
fi
