## /usr/lib/netctl/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" ]]
}

## Retrieve 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=}"
}

## Wait 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[@]}"'
}

## Wait 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[@]}"'
}

## Wait for the WPA supplicant to finish association and authentication
# $1: timeout
# $2: interface name
# $3...: additional accepted states
wpa_wait_until_completed() {
    local timeout=$1 interface=$2
    shift 2
    if ! wpa_wait_until_state "$timeout" "$interface" "COMPLETED" "$@"; then
        report_error "WPA association/authentication failed for interface '$interface'"
        report_debug "WPA state for interface '$interface': $(wpa_get_state "$interface")"
        return 1
    fi
}

## 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
    local pidfile="/run/wpa_supplicant-$interface.pid"
    if [[ $3 ]]; then
        configuration="-c$3"
    else
        WPA_CTRL_DIR="/run/wpa_supplicant"
        configuration="-C$WPA_CTRL_DIR"
    fi

    if ! type wpa_supplicant &> /dev/null; then
        report_error "You need to install 'wpa_supplicant'"
        return 127
    fi

    do_debug wpa_supplicant -q -B -P "$pidfile" -i "$interface" -D "$driver" \
                            "$configuration" $WPAOptions >&2

    # Wait up to one second for the pid file to appear
    timeout_wait 1 '[[ -f $pidfile ]]'
}

## Stop an instance of the WPA supplicant
# $1: interface name
wpa_stop() {
    local interface=$1 pidfile="/run/wpa_supplicant-$1.pid"
    # 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_CTRL_DIR || -e "$WPA_CTRL_DIR/$interface" ]] || return 0

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

## Scan for networks within range
# $1: interface name
# $2: fields of the wpa_supplicant scan_results
#     1: bssid  2: frequency  3: signal level  4: flags  5: ssid
wpa_supplicant_scan() {
    local interface=$1 fields=$2 spawned_wpa=no 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=yes
    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 |     # Remove extraneous output
        sort -rn -k3 |  # Sort by signal strength
        sort -u -k5 |   # Remove duplicates
        sort -rn -k3 |  # Re-sort as the removal disorders the list
        cut -f"$fields"  > "$essids"

    if is_yes "$spawned_wpa"; then
        wpa_stop "$interface"
    fi

    # 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
}

## Unquote a string returned by wpa_call
## Possible representations of string: "string" 737472696e67 string
# $1: string
wpa_unquote() {
    local string="$1"
    if [[ $string == \"*\" ]]; then
        printf '%s' "${string:1:-1}"
    elif [[ $string == +([[:xdigit:]][[:xdigit:]]) ]]; then
        while [[ $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 == +([[:xdigit:]][[: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"
    [[ $Frequency ]] && echo "frequency=$Frequency"
    [[ $Priority ]] && echo "priority=$Priority"
}

## Remove a configuration file for wpa_supplicant
# $1: interface name
wpa_destroy_config_file() {
    local config_file="$STATE_DIR/wpa_supplicant-$1.conf"
    if [[ -f $config_file && -O $config_file ]]; then
        do_debug rm -f "$config_file"
    fi
}


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