## /usr/lib/network/globals needs to be sourced before this file


## Wrapper around wpa_cli to deal with supplicant configurations that set a
## non-standard control path
# $1: interface name
# $2...: call to the supplicant
wpa_call() {
    local args=( "-i" "$1" )
    shift

    if [[ $WPA_CTRL_DIR ]]; then
        args+=("-p" "$WPA_CTRL_DIR")
    elif [[ $WPAConfigFile ]] && grep -q "^[[:space:]]*ctrl_interface=" "$WPAConfigFile"; then
        WPA_CTRL_DIR=$(sed -n "0,/^[[:space:]]*ctrl_interface=/s///p" "$WPAConfigFile")
        if [[ $WPA_CTRL_DIR == DIR=* ]]; then
            WPA_CTRL_DIR=${WPA_CTRL_DIR:4}
            WPA_CTRL_DIR=${WPA_CTRL_DIR%% GROUP=*}
        fi
        args+=( "-p" "$WPA_CTRL_DIR" )
    fi
    do_debug wpa_cli "${args[@]}" "$@"
}

## Check if an instance of the wpa supplicant is active on an interface
# $1: interface name
wpa_is_active(){
    [[ $(wpa_call "$1" ping 2> /dev/null) == "PONG" ]]
}

## Retrieves the state of an instance of the wpa supplicant
## Displays one of: DISCONNECTED, INTERFACE_DISABLED, INACTIVE, SCANNING,
##                  AUTHENTICATING, ASSOCIATING, ASSOCIATED, 4WAY_HANDSHAKE,
##                  GROUP_HANDSHAKE, COMPLETED
# $1: interface name
wpa_get_state() {
    local state
    state=$(wpa_call $1 status | grep -m1 "^wpa_state=") || return 1
    echo ${state#wpa_state=}
}

## Waits until the wpa supplicant reaches a listed state
# $1: timeout
# $2: interface name
# $3...: accepted states
wpa_wait_until_state() {
    local timeout=$1 interface=$2 states=( "${@:3}" )
    timeout_wait "$timeout" \
                 'in_array "$(wpa_get_state "$interface")" "${states[@]}"'
}

## Waits while the wpa supplicant is in a listed state
# $1: timeout
# $2: interface name
# $3...: rejected states
wpa_wait_while_state() {
    local timeout=$1 interface=$2 states=( "${@:3}" )
    timeout_wait "$timeout" \
                 '! in_array "$(wpa_get_state "$interface")" "${states[@]}"'
}

## Start an instance of the wpa supplicant
# $1: interface name
# $2: interface driver(s)
# $3: configuration file
wpa_start() {
    local interface=$1 driver=$2 configuration=$3
    local pidfile="/run/wpa_supplicant_$interface.pid"

    if [[ $configuration ]]; then
        configuration="-c$configuration"
    else
        WPA_CTRL_DIR="/run/wpa_supplicant"
        configuration="-C$WPA_CTRL_DIR"
    fi

    do_debug wpa_supplicant -B -P "$pidfile" -i "$interface" -D "$driver" \
                            "$configuration" $WPAOptions

    # Wait up to one second for the pid file to appear
    if ! timeout_wait 1 '[[ -f "$pidfile" ]]'; then
        # Remove the configuration file if it was generated
        configuration="$STATE_DIR/wpa_supplicant_$interface.conf"
        [[ -f $configuration && -O $configuration ]] && rm -f "$configuration"
        return 1
    fi
}

## Stop an instance of the wpa supplicant
# $1: interface name
wpa_stop() {
    local interface=$1 config_file="$STATE_DIR/wpa_supplicant_$1.conf"
    # We need this as long as wpa_cli has a different default than netctl
    if [[ -z $WPA_CTRL_DIR && -z $WPAConfigFile ]]; then
        WPA_CTRL_DIR="/run/wpa_supplicant"
    fi

    # Done if wpa_supplicant is already terminated for this interface
    [[ -z $WPA_CTR_DIR || -e "$WPA_CTRL_DIR/$interface" ]] || return

    wpa_call "$interface" terminate > /dev/null
    [[ -f $config_file && -O $config_file ]] && rm -f "$config_file"

    # Wait up to one second for the pid file to be removed
    if ! timeout_wait 1 '[[ ! -f "/run/wpa_supplicant_$interface.pid" ]]'; then
        kill "$(< "/run/wpa_supplicant_$interface.pid")" &> /dev/null
    fi
}

## Scan for networks within range
# $1: interface name
# $2: fields of the wpa_supplicant scan_results
wpa_supplicant_scan() {
    local interface=$1 fields=$2 spawned_wpa=0 essids
    # temp file used, as keeping ESSID's with spaces in their name in arrays
    # is hard, obscure and kinda nasty. This is simpler and clearer.

    [[ $interface ]] || return 1
    essids=$(mktemp --tmpdir essid.XXXXXXXX)

    if ! wpa_is_active "$interface"; then
        wpa_start "$interface" "${WPADriver:-nl80211,wext}" || return 1
        spawned_wpa=1
    fi

    wpa_call "$interface" scan > /dev/null
    # Wait at least 3 seconds for scan results
    sleep 3
    # Sometimes, that is not enough
    wpa_wait_while_state 7 "$interface" "SCANNING"
    wpa_call "$interface" scan_results |
        tail -n+2 |
        sort -rn -k3 |
        sort -u -k5 |
        sort -rn -k3 |
        cut -f"$fields"  > "$essids"

    # Fields are tab delimited
    # Remove extraneous output from wpa_cli
    # Sort by strength
    # Remove duplicates
    # Re-sort by strength as the removal disorders the list
    # Cut to the AP/essid fields only

    (( spawned_wpa == 1 )) && wpa_stop "$interface"

    # File of 0 length: no ssid's
    if [[ ! -s "$essids" ]]; then
        rm -f "$essids"
        return 1
    fi

    echo "$essids"
}

## Print a string within quotes, unless it starts with the "-modifier
## Quoted:     string   "string"   '""string"'
## Non-quoted: \"string "\"string" '"string'
# $1: string
wpa_quote() {
    local string=$1
    if [[ $string == \"* ]]; then
        printf '%s' "${string:1}"
    else
        printf '"%s"' "$string"
    fi
}

## Unquotes a string, i.e. returned from wpa_cli
## Quoted:     "string"     -> string
## Non-Quoted: string       -> string
## Hex:        737472696e67 -> string
wpa_unquote() {
    local string="$1"
    if [[ ${string:0:1} == '"' && ${string:(-1)} == '"' ]]; then
        printf "%s" "${string:1:-1}"
    elif [[ "$string" =~ ^([[:xdigit:]]{2})+$ ]]; then
        while [[ -n "$string" ]]; do
            printf "\x"${string:0:2}
            string=${string:2}
        done
    else
        printf "%s" "${string}"
    fi
}

## Create a configuration file for wpa_supplicant without any network blocks
# $1: interface name
wpa_make_config_file() {
    local config_file="$STATE_DIR/wpa_supplicant_$1.conf"

    if [[ -e $config_file ]]; then
        report_debug "The anticipated filename '$config_file' is not available"
        return 1
    fi
    mkdir -p "$STATE_DIR" /run/wpa_supplicant
    if ! : > "$config_file"; then
        report_debug "Could not create the configuration file '$config_file'"
        return 1
    fi

    echo "ctrl_interface=/run/wpa_supplicant" >> "$config_file"
    echo "ctrl_interface_group=${WPAGroup:-wheel}" >> "$config_file"
    [[ $Country ]] && echo "country=$Country" >> "$config_file"
    if is_yes "${AdHoc:-no}"; then
        echo "ap_scan=2" >> "$config_file"
    fi
    echo "$config_file"
}

## Generate a network block for wpa_supplicant
# $Security: type of wireless security
wpa_make_config_block() {
    case $Security in
      none)
        echo "key_mgmt=NONE"
      ;;
      wep)
        echo "key_mgmt=NONE"
        echo "wep_tx_keyidx=0"
        if (( ${#Key} % 2 == 0 )) && [[ "$key" = +([[:xdigit:]]) ]]; then
            echo "wep_key0=$Key"
        else
            echo "wep_key0=$(wpa_quote "$Key")"
        fi
      ;;
      wpa)
        echo "proto=RSN WPA"
        if [[ "${#key}" -eq 64 && "$key" = +([[:xdigit:]]) ]]; then
            echo "psk=$Key"
        else
            echo "psk=$(wpa_quote "$Key")"
        fi
      ;;
      wpa-configsection)
        printf "%s\n" "${WPAConfigSection[@]}"
        return
      ;;
      *)
        report_error "Unsupported security setting: '$Security'"
        return 1
      ;;
    esac

    echo "ssid=$(wpa_quote "$ESSID")"
    [[ $AP ]] && echo "bssid=${AP,,}"
    is_yes "${Hidden:-no}" && echo "scan_ssid=1"
    is_yes "${AdHoc:-no}" && echo "mode=1"
    [[ $ScanFrequencies ]] && echo "scan_freq=$ScanFrequencies"
    [[ $Priority ]] && echo "priority=$Priority"
}


# vim: ft=sh ts=4 et sw=4:
