#!/bin/sh
# Copyright (c) 2015-2018 Qualcomm Technologies, Inc.
#
# All Rights Reserved.
# Confidential and Proprietary - Qualcomm Technologies, Inc.
#
# 2015-2016 Qualcomm Atheros, Inc.
#
# All Rights Reserved.
# Qualcomm Atheros Confidential and Proprietary.

WIFIMON_DEBUG_OUTOUT=0

# Set this to a filename to log all commands executed.
# The output of relevant commands will be appended to the file.
WIFIMON_DEBUG_COMMAND_FILE=

WIFIMON_STATE_NOT_ASSOCIATED='NotAssociated'
WIFIMON_STATE_AUTOCONFIG_IN_PROGRESS='AutoConfigInProgress'
WIFIMON_STATE_MEASURING='Measuring'
WIFIMON_STATE_WPS_TIMEOUT='WPSTimeout'
WIFIMON_STATE_ASSOC_TIMEOUT='AssocTimeout'
WIFIMON_STATE_RE_BACKHAUL_GOOD='RE_BackhaulGood'
WIFIMON_STATE_RE_BACKHAUL_FAIR='RE_BackhaulFair'
WIFIMON_STATE_RE_BACKHAUL_POOR='RE_BackhaulPoor'
WIFIMON_STATE_RE_SWITCH_BSTA='RE_SwitchingBSTA'

WIFIMON_PIPE_NAME='/var/run/repacd.pipe'

. /lib/functions.sh
. /lib/functions/whc-network.sh

# State information
sta_iface_24g='' sta_iface_24g_config_name=''
sta_iface_5g='' sta_iface_5g_config_name='' unknown_ifaces=0
assoc_timeout_logged=0 wps_timeout_logged=0
wps_in_progress=0 wps_start_time=''
wps_stabilization=0 wps_assoc_count=0
assoc_start_time='' last_assoc_state=0
ping_running=0 last_ping_gw_ip=
rssi_num=0 rssi_filename=
bsta_max_preference=

# Config parameters
rssi_samples=''
backhaul_rssi_2='' backhaul_rssi_5='' backhaul_rssi_offset=''
min_wps_assoc=
assoc_timeout='' wps_timeout=''
measuring_cnt=0 measuring_attempts=0
force_bsses_down_on_all_bsta_switches=0
cnt_5g_attempts=0 max_5g_attempts=0

# Emit a message at debug level.
# input: $1 - the message to log
__repacd_wifimon_debug() {
    local stderr=''
    if [ "$WIFIMON_DEBUG_OUTOUT" -gt 0 ]; then
        stderr='-s'
    fi

    logger $stderr -t repacd.wifimon -p user.debug "$1"
}

# Log the output of a command to a file (when enabled).
# This is a nop unless WIFIMON_DEBUG_COMMAND_FILE is set.
# input: $1 - command output to log
__repacd_wifimon_dump_cmd() {
    if [ -n "$WIFIMON_DEBUG_COMMAND_FILE" ]; then
        touch $WIFIMON_DEBUG_COMMAND_FILE
        { date; echo "$1"; echo; } >> $WIFIMON_DEBUG_COMMAND_FILE
    fi
}

# Emit a message at info level.
__repacd_wifimon_info() {
    local stderr=''
    if [ "$WIFIMON_DEBUG_OUTOUT" -gt 0 ]; then
        stderr='-s'
    fi

    logger $stderr -t repacd.wifimon -p user.info "$1"
}

# Obtain a timestamp from the system.
#
# These timestamps will be monontonically increasing and be unaffected by
# any time skew (eg. via NTP or manual date commands).
#
# output: $1 - the timestamp as an integer (with any fractional time truncated)
__repacd_wifimon_get_timestamp() {
    timestamp=$(cut -d' ' -f1 < /proc/uptime | cut -d. -f 1)
    eval "$1=$timestamp"
}

# Terminate any background ping that may be running.
# If no background pings are running, this will be a nop.
__repacd_stop_ping() {
    if [ "$ping_running" -gt 0 ]; then
        kill "$(jobs -p)"
        ping_running=0
        __repacd_wifimon_debug "Stopped ping to GW IP $last_ping_gw_ip"
    fi

    if [ -n "$rssi_filename" ]; then
        # Clean up the temporary file
        rm -f $rssi_filename
        rssi_filename=
    fi
}

# Start a background ping to the gateway address (if it can be resolved).
# This helps ensure the RSSI values are updated (as firmware will not report
# updates if only beacons are being received on the STA interface).
# input: $1 - network: the name of the network being managed
# return: 0 if the ping was started or is already running; otherwise 1
__repacd_start_ping() {
    gw_ip=$(route -n | grep ^0.0.0.0 | grep "br-$1" | awk '{print $2}')
    if [ -n "$gw_ip" ]; then
        if [ ! "$gw_ip" = "$last_ping_gw_ip" ]; then
            # First need to kill the existing one due to the IP change.
            __repacd_stop_ping
            # This will leave ping_running set to 0.
        fi

        if [ "$ping_running" -eq 0 ]; then
            __repacd_wifimon_debug "Pinging GW IP $gw_ip"

            # Unfortunately the busybox ping command does not support an
            # interval. Thus, we can only ping once per second so there will
            # only be a handful of measurements over the course of our RSSI
            # sampling.
            ping "$gw_ip" > /dev/null &
            ping_running=1
            last_ping_gw_ip=$gw_ip
        fi

        # Ping is running now or was started.
        return 0
    fi

    __repacd_wifimon_info "Failed to resolve GW when starting ping; will re-attempt"
    return 1
}

# Determine if the gateway is reachable.
#
# Ideally this would be limited to only the 5 GHz STA interface, but there
# is no good way to do this (since packets would need to be received on the
# bridge interface).
#
# return: 0 if the gateway is reachable; otherwise 1
__repacd_is_gw_reachable() {
    if [ -n "$last_ping_gw_ip" ]; then
        if ping -c 1 -W 1 "${last_ping_gw_ip}" > /dev/null; then
            return 0
        fi
    fi

    # Gateway is unknown or is not reachable
    return 1
}

# Determine if the STA interface named is current associated and active.
#
# input: $1 - sta_iface: the name of the interface (eg. ath01)
# return: 0 if associated; 1 if not associated or empty interface name
__repacd_wifimon_is_active_assoc() {
    local sta_iface=$1
    if [ -n "$sta_iface" ]; then
        local assoc_str=
        assoc_str=$(iwconfig "$sta_iface")
        __repacd_wifimon_dump_cmd "State of $sta_iface: $assoc_str"

        if echo "$assoc_str" | grep 'Access Point: ' | grep -v 'Not-Associated' > /dev/null; then
            return 0
        else
            return 1
        fi
    else
        # An unknown STA interface is considered not associated.
        return 1
    fi
}

# Determine if the STA interface named is current associated.
#
# Note that for the purposes of this function, an empty interface name is
# considered associated. This is done because in some configurations, only
# one interface is enabled.
#
# input: $1 - sta_iface: the name of the interface (eg. ath01)
# return: 0 if associated or if the interface name is empty; otherwise 1
__repacd_wifimon_is_assoc() {
    local sta_iface=$1
    if [ -n "$sta_iface" ];
    then
        if __repacd_wifimon_is_active_assoc "$sta_iface"; then
            return 0
        else
            return 1
        fi
    else
        # An unknown STA interface is considered associated.
        return 0
    fi
}

# Determine if the STA association is stable enough to be able to start
# the next step of the process.
# return: 0 if the association is stable; non-zero if it is not yet deemed
#         stable
__repacd_wifimon_is_assoc_stable() {
    if [ "$wps_stabilization" -gt 0 ]; then
        if [ "$wps_assoc_count" -ge "$min_wps_assoc" ]; then
            return 0
        else
            return 1
        fi
    else
        # No stabilization in progress
        return 0
    fi
}

# Determine if the STA is associated and update the state accordingly.
# input: $1 - network: the name of the network being managed
# input: $2 - cur_re_mode: the currently configured range extender mode
# input: $3 - cur_re_submode: the currently configured range extender sub-mode
# input: $4 - whether this is a check during init for a restart triggered
#             by mode switching
# output: $5 - state: the variable to update with the new state name (if there
#                     was a change)
# output: $6 - re_mode: the desired range extender mode
# output: $7 - re_submode: the desired range extender sub-mode
# return: 0 if associated; otherwise 1
__repacd_wifimon_check_associated() {
    local network=$1
    local associated=0

    if __repacd_wifimon_is_assoc $sta_iface_24g \
        && __repacd_wifimon_is_assoc $sta_iface_5g; then
        associated=1
    fi

    if [ "$associated" -gt 0 ]; then
        # Only update the LED state if we transitioned from not associated
        # to associated.
        if [ "$last_assoc_state" -eq 0 ]; then
            if [ "$wps_in_progress" -gt 0 ]; then
                # If WPS was triggered, it could take some time for the
                # interfaces to settle into their final state. Thus, update
                # the start time for the measurement to the point at which
                # the WPS button was pressed.
                assoc_start_time=$wps_start_time

                # Clear this as we only want to extend the association time
                # for this one instance. All subsequent ones should be based
                # on the time we detect a disassociation (unless WPS is
                # triggered again).
                wps_start_time=''

                # Clear this flag so that we now use the association timer
                # instead of the WPS one.
                wps_in_progress=0

                wps_stabilization=1
            fi

            if [ "$wps_stabilization" -gt 0 ]; then
                wps_assoc_count=$((wps_assoc_count + 1))
                __repacd_wifimon_debug "Assoc post WPS (#$wps_assoc_count)"
            fi

            if __repacd_wifimon_is_assoc_stable; then
                # Assume we are going into the measuring state. This may be
                # overruled if we decide to switch to a different bSTA
                # interface.
                eval "$5=$WIFIMON_STATE_MEASURING"
                assoc_start_time=''
                last_assoc_state=1
                wps_stabilization=0

                # Restart the measurements. We do not remember any past
                # ones as they might not reflect the current state (eg.
                # if the root AP was moved).
                rssi_num=0

                if [ "$wps_stabilization" -gt 0 ]; then
                    # Once WPS completes, switch to the most preferred bSTA.
                    local sta_iface_config=''
                    if [ -n "$sta_iface_24g" ]; then
                        sta_iface_config="$sta_iface_24g_config_name"
                    else
                        sta_iface_config="$sta_iface_5g_config_name"
                    fi

                    # Be optimistic that the most preferred bSTA will have a
                    # good link, so don't force the BSSes down.
                    local force_bsses_down=0
                    __repacd_wifimon_select_next_bsta "$sta_iface_config" \
                        $force_bsses_down \
                        "$WIFIMON_STATE_MEASURING" "$5"

                    # Although we're stable, since in most cases we're going to
                    # change the bSTA radio, we don't want to proceed into the
                    # measuring state below. If it turns out we didn't switch
                    # to a new radio, we'll pick up the measurements in the
                    # next iteration.
                    return 0
                fi
            else
                # Pretend like we are not associated since we need it to be
                # stable.
                return 1
            fi
        fi

        # Association is considered stable. Measure the link RSSI.
        if [ "$rssi_num" -le "$rssi_samples" ] && \
            __repacd_start_ping "$network"; then
            __repacd_wifimon_measure_link "$network" "$5"
        fi
        return 0
    elif [ "$wps_in_progress" -eq 0 ] && [ "$wps_stabilization" -eq 0 ]; then
        # Record the first time we detected ourselves as not being associated.
        # This will drive a timer in the check function that will change the
        # state if we stay disassociated for too long.
        if [ -z "$assoc_start_time" ]; then
            __repacd_wifimon_get_timestamp assoc_start_time
        fi

        last_assoc_state=0
        eval "$5=$WIFIMON_STATE_NOT_ASSOCIATED"
    elif [ "$wps_in_progress" -gt 0 ]; then
        if [ "$wps_timeout_logged" -eq 0 ] && [ "$assoc_timeout_logged" -eq 0 ]; then
            # Suppress logs after we've timed out
            __repacd_wifimon_debug "Auto config in progress - not assoc"
        fi

        wps_assoc_count=0
    fi

    # Not associated and WPS is in progress. No LED update.
    return 1
}

# Find the median from the samples file.
# This is a crude way to compute the median when the number of
# samples is odd. It is not strictly correct for an even number
# of samples since it does not compute the average of the two
# samples in the middle and rather just takes the lower one, but
# this should be sufficient for our purposes. The average is not
# performed due to the values being on the logarithmic scale and
# because shell scripts do not directly support floating point
# arithmetic.
# input: $1 - Samples filename
# input: $2 - Number of samples in the samples file
# output: $3 - computed Median
__repacd_wifimon_compute_median() {
    local median median_index
    local samples_filename="$1"

    median_index=$((($2 + 1) / 2))
    median=$(sort -n < "$samples_filename" | head -n $median_index | tail -n 1)

    eval "$3=$median"
}

# Measure the RSSI to the serving AP and update the state accordingly.
# input: $1 - network: the name of the network being monitored
# output: $2 - state: the variable to update with the new state name (if there
#                     was a change)
__repacd_wifimon_measure_link() {
    local rssi

    if ! __repacd_is_gw_reachable; then
        if [ -n "$last_ping_gw_ip" ]; then
            __repacd_wifimon_debug "GW ${last_ping_gw_ip} not reachable"
        else
            __repacd_wifimon_debug "GW unknown"
        fi
        return
    fi

    if [ "$rssi_num" -eq 0 ]; then
        if [ "$measuring_cnt" -gt 0 ]; then
            __repacd_wifimon_debug "Measurement failed attempt # $measuring_cnt"
        fi
        measuring_cnt=$((measuring_cnt + 1))
    fi

    if [ "$measuring_cnt" -gt "$measuring_attempts" ]; then
        # Could not complete measurements with this interface as a bSTA.
        #
        # Try switching to the next most preferred interface.
        # Since this interface was so flaky, the next one might be too, so
        # tell the fronthaul manager to force the BSSes down at the next
        # startup.
        local force_bsses_down=1
        __repacd_wifimon_select_next_bsta "$sta_iface_config_name" \
            $force_bsses_down "$WIFIMON_STATE_RE_BACKHAUL_POOR" "$2"
        return
    fi

    # Currently only one STA interface is allocated at a time. Determine the
    # active one so that it can be used to check the RSSI.
    local sta_iface=''
    local rssi_threshold=''
    local rssi_led_state=''  # state of LEDs if above RSSI threshold
    if [ -n "$sta_iface_5g" ]; then
        sta_iface="$sta_iface_5g"
        sta_iface_config_name="$sta_iface_5g_config_name"
        rssi_threshold="$backhaul_rssi_5"
        rssi_led_state="$WIFIMON_STATE_RE_BACKHAUL_GOOD"
    else
        sta_iface="$sta_iface_24g"
        sta_iface_config_name="$sta_iface_24g_config_name"
        rssi_threshold="$backhaul_rssi_2"
        rssi_led_state="$WIFIMON_STATE_RE_BACKHAUL_FAIR"
    fi

    rssi=$(iwconfig $sta_iface | grep 'Signal level' | awk -F'=' '{print $3}' | awk '{print $1}')

    # We explicitly ignore clearly bogus values. -95 dBm has been seen in
    # some instances where the STA is not associated by the time the RSSI
    # check is done. The check against 0 tries to guard against scenarios
    # where the firmware has yet to report an RSSI value (although this may
    # never happen if the RSSI gets primed through the association messaging).
    if [ "$rssi" -gt -95 ] && [ "$rssi" -lt 0 ]; then
        if [ "$rssi_num" -lt "$rssi_samples" ]; then
            __repacd_wifimon_debug "RSSI sample #$rssi_num = $rssi dBm"

            # Ignore the very first sample since it is taken at the same time
            # the ping is started (and thus the RSSI might not have been
            # updated).
            if [ "$rssi_num" -eq 0 ]; then
                rssi_filename=$(mktemp /tmp/repacd-rssi.XXXXXX)
            else
                # Not the first sample
                echo "$rssi" >> "$rssi_filename"
            fi
            rssi_num=$((rssi_num + 1))
        elif [ "$rssi_num" -eq "$rssi_samples" ]; then
            __repacd_wifimon_debug "RSSI sample #$rssi_num = $rssi dBm"

            # We will take one more sample and then draw the conclusion.
            # No further measurements will be taken (although this may be
            # changed in the future).
            echo "$rssi" >> "$rssi_filename"

            # We got the required number of samples, now derive the median rssi.
            local rssi_median
            __repacd_wifimon_compute_median "$rssi_filename" $rssi_num rssi_median
            __repacd_wifimon_debug "Median RSSI = $rssi_median dBm"
            __repacd_wifimon_debug "RSSI threshold = $rssi_threshold dBm"

            measuring_cnt=0
            rssi_num=$((rssi_num + 1))  # to prevent future samples

            # We have our measurement, so the ping is no longer needed.
            __repacd_stop_ping

            # In case we disassociate after this, we will want to start the
            # association timer again, so clear our state of the last time we
            # started it so that it can be started afresh upon disassociation.
            assoc_start_time=''

            if [ "$rssi_median" -gt "$rssi_threshold" ]; then
                local keep_24g_bsta_threshold=$((rssi_threshold + backhaul_rssi_offset))
                if [ "$sta_iface" = "$sta_iface_24g" ]; then
                    if [ "$cnt_5g_attempts" -ge "$max_5g_attempts" ] ||
                        [ "$rssi_median" -lt "$keep_24g_bsta_threshold" ]; then
                        eval "$2=$rssi_led_state"
                    else
                        # 2.4 GHz is good enough that 5 GHz might be viable. Try it again.
                        __repacd_wifimon_debug "2.4 GHz good; considering 5 GHz"

                        # Let's be optimistic that 5 GHz will be good enough
                        # and thus we should leave the BSSes up at startup.
                        local force_bsses_down=0
                        __repacd_wifimon_select_next_bsta "$sta_iface_config_name" \
                            $force_bsses_down "$rssi_led_state" "$2"
                        return
                    fi
                else
                    # 5 GHz bSTA with sufficient; we are done
                    eval "$2=$rssi_led_state"
                fi
            elif [ "$sta_iface" = "$sta_iface_24g" ]; then
                __repacd_wifimon_debug "2.4 GHz bSTA weak; not re-attempting 5 GHz"
                eval "$2=$WIFIMON_STATE_RE_BACKHAUL_POOR"
            else
                # Try next best bSTA radio
                __repacd_wifimon_debug "5 GHz bSTA too weak; trying next radio"

                # Until we know we can get a good bSTA link, let's be
                # conservative and bring the BSSes down at startup.
                local force_bsses_down=1
                __repacd_wifimon_select_next_bsta "$sta_iface_config_name" \
                    $force_bsses_down "$WIFIMON_STATE_RE_BACKHAUL_POOR" "$2"
                return
            fi
        fi
    fi
}

# Determine the radio that is next most preferred radio.
#
# If the current radio's preference value is not set, pick the highest
# preference radio.
#
# input: $1 config: section name
# input: $2 cur_pref: the preference level for the current bSTA
# output: $3 preferred_radio: the radio with the highest preference, but below
#                             the cur_pref value if it is set
__repacd_wifimon_resolve_next_bsta_radio() {
    local config="$1"
    local cur_pref="$2"

    local bsta_preference=''
    config_get bsta_preference "$config" repacd_map_bsta_preference 0

    # Skip radios that have no preference set. Maybe the OEM never wants
    # to use that radio.
    if [ -n "$bsta_preference" ]; then
        if [ -n "$cur_pref" ]; then
            if [ "$bsta_preference" -lt "$cur_pref" ] &&
                [ "$bsta_preference" -gt "$bsta_max_preference" ]; then
                eval "$3=$config"
                bsta_max_preference="$bsta_preference"
            fi
        else
            if [ "$bsta_preference" -gt "$bsta_max_preference" ]; then
                eval "$3=$config"
                bsta_max_preference="$bsta_preference"
            fi
        fi
    fi
}

# Mark the desired radio as selected for the bSTA.
#
# If a radio is marked as selected (using the repacd_map_bsta_selected config
# option), it will be used. If instead none is marked, the radio with the
# highest repacd_map_bsta_pref value will be used.
#
# input: $1 config: section name
# input: $2 selected_radio: the radio that should be selected
__repacd_wifimon_select_bsta_radio() {
    local config="$1"
    local selected_radio="$2"

    if [ "$config" = "$selected_radio" ]; then
        uci_set wireless "$config" repacd_map_bsta_selected '1'
    else
        uci_set wireless "$config" repacd_map_bsta_selected '0'
    fi
}

# Update the radio on which to instantiate the bSTA.
#
# If the currently selected bSTA is 2.4 GHz, select the highest priority radio.
# Otherwise, select the highest priority radio that is less than the current
# priority.
#
# input: $1 - config: the name of the config section for the bSTA
# input: $2 - force_bsses_down: whether to force the BSSes down on the next
#                               startup
# input: $3 - state_no_bsta_radios: the state to transition into if there are
#                                   no other bSTA radios to switch to
# output: $4 - state: the variable to update with the new state name (if there
#                     was a change)
__repacd_wifimon_select_next_bsta() {
    local config="$1"
    local force_bsses_down="$2"
    local state_no_bsta_radios="$3"

    # Resolve the current preference value
    local device
    config_load wireless
    config_get device "$config" device

    local cur_pref
    config_get cur_pref "$device" repacd_map_bsta_preference

    if [ -n "$sta_iface_24g" ]; then
        # We want to take the maximum one
        cur_pref=''
    fi

    local preferred_radio=''
    bsta_max_preference=0
    config_foreach __repacd_wifimon_resolve_next_bsta_radio wifi-device \
        "$cur_pref" preferred_radio

    if [ "$device" != "$preferred_radio" ]; then
        __repacd_wifimon_debug "Selected $preferred_radio as next bSTA"

        # If switching from 5 GHz to 2.4 GHz, increment the count to record we
        # have tried all 5 GHz bSTAs. The assumption here is that 2.4 GHz will
        # always come after 5 GHz in the preference order.
        if [ -n "$sta_iface_5g" ] && ! whc_is_5g_radio "$preferred_radio"; then
            cnt_5g_attempts=$((cnt_5g_attempts + 1))
            uci_set repacd MAPWiFiLink '5gAttemptsCount' "$cnt_5g_attempts"
            uci_commit repacd

            __repacd_wifimon_debug "Completed 5 GHz bSTA attempt #$cnt_5g_attempts"
        fi

        config_foreach __repacd_wifimon_select_bsta_radio wifi-device \
            "$preferred_radio"
        uci_commit wireless

        if [ "$force_bsses_down_on_all_bsta_switches" -gt 0 ]; then
            force_bsses_down=1
        fi

        __repacd_wifimon_debug "Updating ForceDownOnStart to $force_bsses_down"
        uci_set repacd FrontHaulMgr ForceDownOnStart "$force_bsses_down"
        uci_commit repacd

        eval "$4=$WIFIMON_STATE_RE_SWITCH_BSTA"
    else
        __repacd_wifimon_debug "No other bSTA radios available"
        eval "$4=$state_no_bsta_radios"
    fi
}

# Determine if a provided amount of time has elapsed.
# input: $1 - start_time: the timestamp (in seconds)
# input: $2 - duration: the amount of time to check against (in seconds)
# return: 0 on timeout; non-zero if no timeout
__repacd_wifimon_is_timeout() {
    local start_time=$1
    local duration=$2

    # Check if the amount of elapsed time exceeds the timeout duration.
    local cur_time
    __repacd_wifimon_get_timestamp cur_time
    local elapsed_time=$((cur_time - start_time))
    if [ "$elapsed_time" -gt "$duration" ]; then
        return 0
    fi

    return 1
}

# Check whether the given interface is the STA interface on the desired
# network and the desired band.
#
# input: $1 - config: the name of the interface config section
# input: $2 - network: the name of the network to which the STA interface
#                      must belong to be matched
# output: $3 - iface: the resolved STA interface name on 2.4 GHz (if found)
# output: $4 - iface_config_name: the resolved name of the config section
#                                 for the STA interface on 2.4 GHz (if found)
# output: $5 - iface: the resolved STA interface name on 5 GHz (if found)
# output: $6 - iface_config_name: the resolved name of the config section
#                                 for the STA interface on 5 GHz (if found)
# output: $7 - unknown_ifaces: whether any Wi-Fi interfaces are as yet
#                              unknown (in terms of their interface name)
__repacd_wifimon_is_sta_iface() {
    local config="$1"
    local network_to_match="$2"
    local iface disabled mode device hwmode

    config_get network "$config" network
    config_get iface "$config" ifname
    config_get disabled "$config" disabled '0'
    config_get mode "$config" mode
    config_get device "$config" device
    config_get hwmode "$device" hwmode

    if [ "$hwmode" != "11ad" ]; then
        if [ "$network" = "$network_to_match" ] && [ -n "$iface" ] && [ "$mode" = "sta" ] && \
            [ "$disabled" -eq 0 ]; then
            if whc_is_5g_vap "$config"; then
                eval "$5=$iface"
                eval "$6=$config"
            else
                eval "$3=$iface"
                eval "$4=$config"
            fi
        elif [ -z "$iface" ] && [ "$disabled" -eq 0 ]; then
            # If an interface is showing as enabled but no name is known for it,
            # mark it as such. Without doing this, we can resolve the interface
            # names improperly.
            eval "$7=1"
        fi
    fi
}

# Initialize the sta_iface_5g variable with the STA interface that is enabled
# on the specified network (if any).
# input: $1 - network: the name of the network being managed
__repacd_wifimon_get_sta_iface() {
    unknown_ifaces=0

    config_load wireless
    config_foreach __repacd_wifimon_is_sta_iface wifi-iface "$1" \
        sta_iface_24g sta_iface_24g_config_name \
        sta_iface_5g sta_iface_5g_config_name unknown_ifaces

    if [ "$unknown_ifaces" -gt 0 ]; then
        # Clear out everything because we cannot be certain we have the
        # right names (eg. interfaces may not all be up yet).
        sta_iface_24g=
        sta_iface_24g_config_name=
        sta_iface_5g=
        sta_iface_5g_config_name=
    fi
}

# Initialize the Wi-Fi monitoring logic with the name of the network being
# monitored.
# input: $1 - network: the name of the network being managed
# input: $2 - cur_re_mode: the current operating range extender mode
# input: $3 - cur_re_submode: the current operating range extender submode
# input: $4 - autoconfig: whether it was an auto-config restart
# output: $5 - state: the name of the initial state
# output: $6 - new_re_mode: the resolved range extender mode
# output: $7 - new_re_submode: the resolved range extender submode
repacd_wifimon_init() {
    # Resolve the STA interfaces.
    # Here we assume that if we have the 5 GHz interface, that is sufficient,
    # as not all modes will have a 2.4 GHz interface.
    __repacd_wifimon_get_sta_iface "$1"
    if [ -n "$sta_iface_5g" ] || [ -n "$sta_iface_24g" ]; then
        if [ -n "$sta_iface_24g" ]; then
            __repacd_wifimon_debug "Resolved 2.4 GHz STA interface to $sta_iface_24g"
            __repacd_wifimon_debug "2.4 GHz STA interface section $sta_iface_24g_config_name"
        fi

        if [ -n "$sta_iface_5g" ]; then
            __repacd_wifimon_debug "Resolved 5 GHz STA interface to $sta_iface_5g"
            __repacd_wifimon_debug "5 GHz STA interface section $sta_iface_5g_config_name"
        fi

        # First resolve the config parameters.
        config_load repacd
        config_get min_wps_assoc 'MAPWiFiLink' 'MinAssocCheckPostWPS' '5'
        config_get wps_timeout 'MAPWiFiLink' 'WPSTimeout' '120'
        config_get assoc_timeout 'MAPWiFiLink' 'AssociationTimeout' '300'
        config_get rssi_samples 'MAPWiFiLink' 'RSSINumMeasurements' '5'
        config_get backhaul_rssi_2 'MAPWiFiLink' 'BackhaulRSSIThreshold_2' '-78'
        config_get backhaul_rssi_5 'MAPWiFiLink' 'BackhaulRSSIThreshold_5' '-78'
        config_get backhaul_rssi_offset 'MAPWiFiLink' 'BackhaulRSSIThreshold_offset' '10'
        config_get max_5g_attempts 'MAPWiFiLink' 'Max5gAttempts' '3'
        config_get cnt_5g_attempts 'MAPWiFiLink' '5gAttemptsCount' '0'
        config_get measuring_attempts 'MAPWiFiLink' 'MaxMeasuringStateAttempts' '3'
        config_get force_bsses_down_on_all_bsta_switches 'MAPWiFiLink' \
            'ForceBSSesDownOnAllBSTASwitches' '0'

        # Create ourselves a named pipe so we can be informed of WPS push
        # button events.
        if [ -e $WIFIMON_PIPE_NAME ]; then
            rm -f $WIFIMON_PIPE_NAME
        fi

        mkfifo $WIFIMON_PIPE_NAME

        # If already associated, go to the InProgress state.
        __repacd_wifimon_check_associated "$1" "$2" "$3" "$4" "$5" "$6" "$7"
    fi
    # Otherwise, must be operating in CAP mode.
}

# Check the status of the Wi-Fi link (WPS, association, and RSSI).
# input: $1 - network: the name of the network being managed
# input: $2 - cur_re_mode: the currently configured range extender mode
# input: $3 - cur_re_submode: the currently configured range extender sub-mode
# output: $4 - state: the name of the new state (only set upon a change)
# output: $5 - re_mode: the desired range extender mode (updated only once
#                       the link to the AP is considered stable)
# output: $6 - re_submode: the desired range extender submode (updated only once
#                       the link to the AP is considered stable)
repacd_wifimon_check() {
    local wps_pressed=0
    if [ -n "$sta_iface_24g" ] || [ -n "$sta_iface_5g" ]; then
        local wps_pbc
        if read -r -t 1 wps_pbc <>$WIFIMON_PIPE_NAME; then
            __repacd_wifimon_debug "Received $wps_pbc on wifimon pipe"

            eval "$4=$WIFIMON_STATE_AUTOCONFIG_IN_PROGRESS"
            wps_in_progress=1
            __repacd_wifimon_get_timestamp wps_start_time
        fi

        if __repacd_wifimon_check_associated "$1" "$2" "$3" 0 "$4" "$5" "$6"; then
            assoc_timeout_logged=0
            wps_timeout_logged=0
        else  # not associated
            # If the WPS button was pressed, do not proceed to checking the
            # association timeouts. That will happen in subsequent checks.
            if [ "$wps_pressed" -gt 0 ]; then
                return 1
            fi

            if [ "$wps_in_progress" -gt 0 ]; then
                if __repacd_wifimon_is_timeout $wps_start_time $wps_timeout; then
                    if [ "$wps_timeout_logged" -eq 0 ]; then
                        __repacd_wifimon_debug "WPS timeout"
                        wps_timeout_logged=1
                    fi

                    eval "$4=$WIFIMON_STATE_WPS_TIMEOUT"
                fi
            else
                if __repacd_wifimon_is_timeout $assoc_start_time $assoc_timeout; then
                    if [ "$assoc_timeout_logged" -eq 0 ]; then
                        __repacd_wifimon_debug "Association timeout"
                        assoc_timeout_logged=1
                    fi

                    if [ -n "$sta_iface_5g" ]; then
                        # Try falling back to another interface
                        __repacd_wifimon_debug "Trying next bSTA radio"

                        # Since this bSTA could not associate, assume the next
                        # one might not be able to either. Thus, tell the
                        # fronthaul manager to force the BSSes down at startup.
                        local force_bsses_down=1
                        __repacd_wifimon_select_next_bsta "$sta_iface_5g_config_name" \
                            $force_bsses_down \
                            "$WIFIMON_STATE_AUTOCONFIG_IN_PROGRESS" "$4"
                    else
                        # Already on 2.4 GHz. If we cannot associate here, we
                        # probably cannot associate on 5 GHz.
                        eval "$4=$WIFIMON_STATE_ASSOC_TIMEOUT"
                    fi
                fi
            fi
        fi
    fi
}

# Hook function that, in SON mode, would determine whether to bring up/down the
# 2.4 GHz backhaul. In Multi-AP SIG mode, this is not currently relevant
# (given the 1 backhaul link assumption).
repacd_wifimon_independent_channel_check() {
    /bin/true
}

# Terminate the Wi-Fi link monitoring, cleaning up any state in preparation
# for shutdown.
repacd_wifimon_fini() {
    /bin/true
}

# Resolve the credentials for any VAP that has bBSS support.
#
# input: $1 - config: the name of the interface config section
# input: $2 - network: the name of the network to which the AP interface
#                      must belong to be matched
# output: $3 - ssid: the backhaul SSID
# output: $4 - key: the backhaul key (aka. passphrase)
# output: $5 - enc: the backhaul encryption mode
__repacd_wifimon_resolve_bbss_creds() {
    local config="$1"
    local network_to_match="$2"

    local network mode device hwmode disabled
    local ssid key enc map map_type

    config_get network "$config" network
    config_get mode "$config" mode
    config_get device "$config" device
    config_get hwmode "$device" hwmode
    config_get disabled "$config" disabled '0'

    config_get ssid "$config" ssid
    config_get key "$config" key
    config_get enc "$config" encryption
    config_get map "$config" map '0'
    config_get map_type "$config" MapBSSType

    if [ "$hwmode" != "11ad" ]; then
        if [ "$network" = "$network_to_match" ] && [ "$mode" = "ap" ] && \
            [ "$disabled" -eq 0 ] && [ "$map" -gt 0 ]; then
            local bbss=$(( map_type & 64 ))
            if [ ${bbss} -eq 64 ]; then
                # Has bBSS capabilty, so record the credentials
                eval "$3='${ssid}'"
                eval "$4='${key}'"
                eval "$5='${enc}'"
            fi
        fi
    fi
}

# Set the credentials on the bSTA VAP(s).
#
# input: $1 - config: the name of the interface config section
# input: $2 - network: the name of the network to which the STA interface
#                      must belong to be matched
# input: $3 - ssid: the backhaul SSID
# input: $4 - key: the backhaul key (aka. passphrase)
# input: $5 - enc: the backhaul encryption mode
__repacd_wifimon_set_bsta_creds() {
    local config="$1"
    local network_to_match="$2"
    local ssid="$3"
    local key="$4"
    local enc="$5"

    local network mode hwmode device disabled
    local ssid key enc map map_type

    config_get network "$config" network
    config_get mode "$config" mode
    config_get device "$config" device
    config_get hwmode "$device" hwmode
    config_get disabled "$config" disabled '0'

    config_get map "$config" map '0'
    config_get map_type "$config" MapBSSType

    if [ "$hwmode" != "11ad" ]; then
        if [ "$network" = "$network_to_match" ] && [ "$mode" = "sta" ] && \
            [ "$map" -gt 0 ]; then
            if [ "$map_type" -eq 128 ]; then  # bSTA
                uci_set wireless "$config" ssid "$ssid"
                uci_set wireless "$config" key "$key"
                uci_set wireless "$config" encryption "$enc"
                uci_commit wireless
            fi
        fi
    fi
}

# Copy the backhaul credentials from a bBSS interface (or a combined
# fBSS + bBSS) to the bSTA interface. This ensures the credentials obtained
# during Ethernet onboarding can be used when switching into RE mode.
repacd_wifimon_config_bsta() {
    bbss_ssid=''
    bbss_key=''
    bbss_enc=''

    # First resolve the SSID, passphrase, and encryption from the bBSS
    # capable AP interfaces.
    config_load wireless
    config_foreach __repacd_wifimon_resolve_bbss_creds wifi-iface "$1" \
        bbss_ssid bbss_key bbss_enc

    # Now apply these values to the bSTA interface.
    config_foreach __repacd_wifimon_set_bsta_creds wifi-iface "$1" \
        "${bbss_ssid}" "${bbss_key}" "${bbss_enc}"
}
