#!/bin/bash

#****************************************************************************
# tools/simwifi/sim_wifi.sh
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.  The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# License for the specific language governing permissions and limitations
# under the License.
#
#****************************************************************************
#

NUTTX_BR_IF="nuttx0"
RUN_DIR="/var/run/simwifi"
LINK_DIR="/usr/bin"
CUR_DIR=""
DBG_LEVEL=1

WPA_PID_FILE="$RUN_DIR/wpa_supplicant.pid"
WPA_CONF_FILE="$RUN_DIR/wpa_supplicant.conf"
HOSTAPD_PID_FILE="$RUN_DIR/hostapd.pid"
HOSTAPD_CONF_FILE="$RUN_DIR/hostapd.conf"
DNSMASQ_PID="$RUN_DIR/dnsmasq.pid"
DNSMASQ_CONF="$RUN_DIR/dnsmasq.conf"
UDHCPC_PID="$RUN_DIR/udhcpc.pid"
UDHCPC_SCRIPT="/var/udhcpc.script"

LOG_FILE="$RUN_DIR/simwifi.log"
STATE_FILE="$RUN_DIR/simwifi.state"
DEFCONF_FILE="$RUN_DIR/simwifi.conf"

DHCP_CLIENT=$(which udhcpc)
DNSMASQ=$(which dnsmasq)
WPA_SUPPLICANT=$(which wpa_supplicant)
HOSTAPD=$(which hostapd)

# print the debug message
sw_dbg()
{
  [ $1 -ge $DBG_LEVEL ] && {
    echo "$2"
  }
}

# get var from the file
# $1:key $2:file
get_var()
{
  cat $2 | grep $1 | awk -F':' '{print $2}'
}

check_state()
{
  old_state=$(get_var state $STATE_FILE)
  sw_dbg 1 "new state:$1, old_state:$old_state"

  if [ "$1" = "$old_state" ]; then
    sw_dbg 1 "cur_state is $1"
    exit 0
  fi
}

# set simwifi state
set_state()
{
  state=$1
  wlan_if=$2
  br_if=$3
  wan_if=$4
  sw_dbg 1 "[set_state] new state:$1"

  if [ "$state" != "SW_INIT" -a "$state" != "SW_HWSIM" -a \
       "$state" != "SW_NET" -a "$state" != "SW_STA" -a \
       "$state" != "SW_AP" -a "$state" != "SW_STAAP" ]; then
    echo "no state: $1"
    exit -1
  fi

  sw_dbg 1 "state:$state wlan_if:$wlan_if, br_if:$br_if wan_if:$wan_if"

  echo "state:$state" > $STATE_FILE
  [ -n "$wlan_if" ] && echo "wlan:$wlan_if" >> $STATE_FILE
  [ -n "$br_if" ] && echo "br:$br_if" >> $STATE_FILE
  [ -n "$wan_if" ] && echo "wan:$wan_if" >> $STATE_FILE
}

# recover the simwifi state to SW_INIT

recovery_to_init()
{
  cur_state=$(get_var state $STATE_FILE)
  wlan_if=$(get_var wlan $STATE_FILE)
  br_if=$(get_var br $STATE_FILE)

  sw_dbg 1 "[recovery_to_init] cur_s:$cur_state"
  case $cur_state in
    SW_INIT) ;;
    SW_HWSIM) stop_hwsim;;
    SW_STAAP) stop_staap;;
    SW_NET) stop_net $wlan_if;;
    SW_STA) stop_sta;;
    SW_AP) stop_ap $wlan_if;;
    *)  set_state SW_INIT;;
  esac
}

# Get the absolute pathname of sim_wifi.sh

get_script_path()
{
  SOURCE=$1

  while [ -h "$SOURCE" ]; do
    SOURCE=$(readlink $SOURCE)
  done

  CUR_DIR=$(cd $(dirname $SOURCE) && pwd)
}

# Copy the configure file to the $RUN_DIR

init_env()
{
  sw_dbg 1 "init env"

  [ -f "$STATE_FILE" ] && {
    check_hwsim_mode
    recovery_to_init
  }

  mkdir -p $RUN_DIR
  touch $STATE_FILE

  if [ "$1" = "hwsim" ]; then
    cp -fr $CUR_DIR/hostapd_hwsim.conf $HOSTAPD_CONF_FILE
    cp -fr $CUR_DIR/wpa_supplicant_hwsim.conf $WPA_CONF_FILE
  else
    cp -fr $CUR_DIR/hostapd.conf $HOSTAPD_CONF_FILE
    cp -fr $CUR_DIR/wpa_supplicant.conf $WPA_CONF_FILE
  fi

  cp -fr $CUR_DIR/dnsmasq.conf $DNSMASQ_CONF
  cp -fr $CUR_DIR/udhcpc.script $UDHCPC_SCRIPT
  chmod +x $UDHCPC_SCRIPT
}

# Rename the interface name

rename_ifdev()
{
  old_name=$1
  new_name=$2

  ifconfig $old_name down && ip link set $old_name name $new_name
  ifconfig $new_name up
}

kill_service()
{
  service_name=$(basename $1 .pid)
  sw_dbg 1 "kill $service_name"
  if [ -f "$1" ]; then
    pid=$(cat $1)
    kill -9 $pid
    rm $1
  else
    sw_dbg 1 "$1 isn't existed."
    killall $service_name
  fi
}

stop_wpa()
{
  kill_service $WPA_PID_FILE
}

stop_hostapd()
{
  kill_service $HOSTAPD_PID_FILE
}

start_hostapd()
{
  sw_dbg 1 "start ap on $1"

  #Waiting 1s. If not, the hostapd starting maybe fail on switching mode.

  sleep 1
  $HOSTAPD -B -i$1 -P $HOSTAPD_PID_FILE $HOSTAPD_CONF_FILE -t &>>$LOG_FILE
}

start_wpa()
{
  sw_dbg 1 "start sta on $1"

  #Waiting 1s. If not, the wap_supplicant starting maybe fail on switching mode.

  sleep 1
  $WPA_SUPPLICANT -B -c $WPA_CONF_FILE -i$1 -P $WPA_PID_FILE  &>>$LOG_FILE
}

start_udhcpc()
{
  sw_dbg 1 "start dhcp client on $1"

  [ -n "$2" ] && script_opt=" -s $UDHCPC_SCRIPT"

  $DHCP_CLIENT -i $1 -p $UDHCPC_PID $script_opt &>>$LOG_FILE &
}

stop_udhcpc()
{
  kill_service $UDHCPC_PID
}

start_bridge()
{
  sw_dbg 1 "start bridge to $1"
  $CUR_DIR/../simhostroute.sh $1 on &>>$LOG_FILE
}

stop_bridge()
{
  sw_dbg 1 "stop bridge to $1"
  sw_dbg 1 "Warning: The $NUTTX_BR_IF will be deleted!"
  $CUR_DIR/../simhostroute.sh $1 off &>>$LOG_FILE
}

start_dhcp_server()
{
  sw_dbg 1 "start dhcp server on $1"
  dbg_option=$($DNSMASQ --help|grep log-debug | awk '{print $1}')

  $DNSMASQ -i$1 -C $DNSMASQ_CONF  $dbg_option -x $DNSMASQ_PID &>>$LOG_FILE
}

stop_dhcp_server()
{
  kill_service $DNSMASQ_PID
}

check_hwsim_mode()
{
  cur_state=$(get_var state $STATE_FILE)

  sw_dbg 1 "[check_hwsim_mode]state: $cur_state"
  if [ "$cur_state" = "SW_HWSIM" ]; then
    sw_dbg 1 "cur_state is hwsim mode. \
          Don't set the sta/ap mode."
    exit 0
  fi
}

start_hwsim()
{
  sta_if=${1:-wlan0}
  ap_if=${2:-wlan1}

  sw_dbg 1 "start hwsim on $sta_if $ap_if"

  init_env hwsim
  start_hostapd $ap_if
  start_wpa $sta_if
  start_dhcp_server $NUTTX_BR_IF

  set_state SW_HWSIM
}

stop_hwsim()
{
  stop_wpa
  stop_hostapd

  stop_dhcp_server

  set_state SW_INIT
}

start_net()
{
  init_env
  start_wpa $1
  start_udhcpc $1
  start_bridge $1
  start_dhcp_server $NUTTX_BR_IF

  set_state SW_NET $1 $NUTTX_BR_IF $1
}

# Warning:The function will delete nuttx0

stop_net()
{
  stop_wpa
  stop_udhcpc
  stop_bridge $1
  stop_dhcp_server

  set_state SW_INIT
}

start_sta()
{
  [ -z "$(ifconfig | grep "$NUTTX_BR_IF")" ] && {
    sw_dbg 1 "Please ensure that the $NUTTX_BR_IF is existed."
    exit -2
  }

  check_state SW_STA
  check_state SW_STAAP

  init_env
  start_wpa $1
  start_udhcpc $1 s
  start_dhcp_server $NUTTX_BR_IF

  set_state SW_STA $1 $NUTTX_BR_IF;

  exit 0
}

del_gw_wlan()
{
  wlan_if=$1
  router=$(ip route show | grep default | grep $wlan_if)

  [ -n "$router" ] && {
    ip route del $router
    sw_dbg 1 "del the default router on $wlan_if"
  }

  ifconfig $wlan_if 0.0.0.0
}

stop_sta()
{
  stop_wpa
  stop_udhcpc
  stop_dhcp_server

  #check and delete default on wlan0

  wlan_if=$(get_var wlan $STATE_FILE)
  del_gw_wlan $wlan_if

  set_state SW_INIT
}

start_ap()
{
  [ -z "$1" ] && {
    echo "Missing parameter wlan interface."
    exit -1
  }

  [ -z "$2" ] && {
    [ -z "$(ifconfig | grep "$NUTTX_BR_IF")" ] && {
      echo "Missing parameter wan interface."
      echo "Please ensure that the $NUTTX_BR_IF is existed."
      exit -2
    }

    wan_if=""
  } || {
    wan_if=$2
  }

  check_state SW_AP
  check_state SW_STAAP

  init_env
  start_hostapd $1

  # nuttx0 doesn't exist and wan_if is configured.

  [ -z "$(ifconfig | grep "$NUTTX_BR_IF")" -a -n "$wan_if" ] && {
    start_bridge $wan_if
  }

  ip link set dev $1 master $NUTTX_BR_IF
  start_dhcp_server $NUTTX_BR_IF

  set_state SW_AP $1 $wan_if;

  exit 0
}

stop_ap()
{
  stop_hostapd
  [ -z "$2" ] || stop_bridge $2

  sw_dbg 1 "stop_ap $1"
  ip link set dev $1 nomaster
  stop_dhcp_server

  set_state SW_INIT
}

check_ifname()
{
  [ -z "$(ifconfig | grep "$1")" ] && {
    echo "The $1 does not exist."
    exit -1
  }
}

start_staap()
{
  sta_if=${1:-wlan0}
  ap_if=${2:-wlan1}
  sw_dbg 1 "start staap $sta_if $ap_if"

  for i in $sta_if $ap_if $NUTTX_BR_IF; do
    check_ifname $i
  done

  check_state SW_STAAP

  init_env
  start_hostapd $ap_if

  sleep 1
  start_wpa $sta_if

  ip link set dev $ap_if master $NUTTX_BR_IF

  start_udhcpc $sta_if
  start_dhcp_server $NUTTX_BR_IF

  set_state SW_STAAP "$sta_if,$ap_if" $NUTTX_BR_IF
}

stop_staap()
{
  stop_wpa
  stop_hostapd

  stop_udhcpc
  stop_dhcp_server
  ip link set dev wlan1 nomaster

  set_state SW_INIT
}

show_process()
{
  ps -ef | grep "$1 " | grep -v grep
}

show_status()
{
  #1.env conf
  [ -d "$RUN_DIR" ] && {
    echo "$RUN_DIR"
    ls $RUN_DIR
  }

  #2. key services
  echo -e "\nservices list"
  for i in wpa_supplicant hostapd dnsmasq udhcpc; do
    show_process $i
  done

  #3. bridge nuttx0 info
  [ -n "$(ifconfig | grep $NUTTX_BR_IF)" ] && {
    echo -e "\nbridge $NUTTX_BR_IF"
    ip link show master $NUTTX_BR_IF
  }

  #4.show DEFCONF_FILE
  echo -e "\ndefault config"
  cat $DEFCONF_FILE

  #5. show state
  echo ""
  cat $STATE_FILE

  #6. show router
  echo ""
  ip route show

  #7. show radio state
  echo "radio status"
  rfkill list

  #8. show networkmanager wifi status
  echo "networkmanager wifi status"
  nmcli radio wifi
}

# $1 is the default wan interface for start_sta
# $2 is the simwifi mode, (rnc/hwsim)

init()
{
  [ -z "$1" ] && {
    echo "Missing the default wan interface."
    exit -1
  }

  [ -z "$2" ] && {
    echo "Missing the simwifi mode."
    exit -2
  }

  init_env

  ln -s $CUR_DIR/sim_wifi.sh $LINK_DIR/sim_wifi.sh

  echo "defwan:$1" > $DEFCONF_FILE
  [ -n "$1" -a  -n "$(ifconfig | grep $1)" ] && start_bridge $1

  echo "mode:$2" >> $DEFCONF_FILE
  [ "$2" = "hwsim" ] && modprobe  mac80211_hwsim

  # Turn off the wifi of networkmanager.
  nmcli radio wifi off

  # open the radio by rfkill
  id_list=$(rfkill list | grep phy | awk -F':' '{print $1}')
  for id in $id_list; do
    rfkill unblock $id;
  done

  set_state SW_INIT  "" $NUTTX_BR_IF $1
}

clean()
{
  [ -z "$1" ] && {
    echo "Missing the default wan interface."
    exit -1
  }

  recovery_to_init

  rm $LINK_DIR/sim_wifi.sh

  cur_mode=$(get_var mode $DEFCONF_FILE)
  [ "$cur_mode" = "hwsim" ] &&  modprobe -r mac80211_hwsim

  echo "defwan:$1" > $DEFCONF_FILE
  [ -n "$1" -a  -n "$(ifconfig | grep $1)" ] && stop_bridge $1

  rm -fr $RUN_DIR
  rm -f $UDHCPC_SCRIPT

  # Turn on the wifi of networkmanager.
  nmcli radio wifi on

}

usage()
{
  echo "$(basename $SOURCE) (rename <old> <new> |"
  echo -e "\t init <wan> <mode> |clean <wan> |"
  echo -e "\t start_wpa <wlan0> |stop_wpa |"
  echo -e "\t start_hostapd <wlan0> |stop_hostapd |"
  echo -e "\t start_udhcpc <wlan0> |stop_udhcpc |"
  echo -e "\t start_dhcp <wlan0> |stop_dhcp |"
  echo -e "\t start_hwsim |stop_hwsim |up_hwsim |"
  echo -e "\t start_staap |stop_staap |"
  echo -e "\t start_net <wlan0> |stop_net <wlan0> |"
  echo -e "\t start_sta <wlan0> |stop_sta |"
  echo -e "\t start_ap <wlan0> [eth0] |stop_ap <wlan0> [eth0] |"
  echo -e "\t start_bridge <eth0> |stop_bridge <eth0> |"
  echo -e "\t show | help)"
}

# locate the directory of the sim_wifi.sh.

get_script_path $0

case $1 in
  init) init $2 $3;;
  clean) clean $2;;
  start_bridge) start_bridge $2;;
  stop_bridge) stop_bridge $2;;
  start_hwsim)  start_hwsim $2 $3;;
  stop_hwsim)   stop_hwsim;;
  up_hwsim) ifconfig hwsim0 up;;
  start_wpa) start_wpa $2;;
  stop_wpa) stop_wpa;;
  start_hostapd) start_hostapd $2;;
  stop_hostapd) stop_hostapd;;
  rename) rename_ifdev $2 $3;;
  start_udhcpc) start_udhcpc $2;;
  stop_udhcpc) stop_udhcpc;;
  start_dhcp) start_dhcp_server $2;;
  stop_dhcp) stop_dhcp_server;;
  start_net) start_net $2;;
  stop_net) stop_net $2;;
  start_sta) start_sta $2;;
  stop_sta) stop_sta;;
  start_ap) start_ap $2 $3;;
  stop_ap) stop_ap $2 $3;;
  start_staap) start_staap;;
  stop_staap) stop_staap;;
  show) show_status;;
  help|*) usage;;
esac
