#!/bin/sh
# udhcpc script originally edited by Tim Riker <Tim@Rikers.org>
# This derivative for leasefail on SP-ICE-3 by jim spriggs <j.spriggs@raylase.de>
#
CMDNAME="${0##*/}"
CARDSERIALNUMBERPROG="/opt/RAYLASE/SPICE3/bin/cardserialnumber"
FIRST_AND_SECOND_OCTETS="169.254"
FLAGS="multicast promisc"
ARPINGPROG="/bin/arping"
MAXUSABLESERIALNUMBER=$(( 2**14 - 2 ))

[[ -z $1 ]] && echo "Error: ${CMDNAME} should be called from udhcpc" && exit 1

BROADCAST="broadcast +"
NETMASK="16"

function log() {
    /usr/bin/logger --id=$$ --tag "${CMDNAME}" "$*"
}


function cardAddrFromSerialNumber()
{
    local sernum=$(( 10#${1:-0} ))
    local cardAddr=1
    if (( sernum < 1 )); then
        : # Simply return "0.1" or "0.3", since "[169.254.]0.0" is not a valid endpoint address
    else
        # Fold the serial number, if necessary, to avoid assigning "[169.254.]255.255" which is also not a valid endpoint address.
        local cardAddr=$((  ( (sernum - 1) % MAXUSABLESERIALNUMBER ) + 1 ))

        # Shift two bits left, leaving room for the interface bit and the anti-collision bit.
        (( cardAddr *= 4 ))
    fi

    # Add in the interface bit.
    if [[ $interface == "eth1" ]]; then
        (( cardAddr += 2 ))
    fi
    printf '%d' $cardAddr
}

function getOctet3FromCardAddr()
{
    # Return the upper 8 bits for octet 3
    printf '%d' $(( ( $1 & 0xFF00 ) / 256 ))
}

function getOctet4FromCardAddr()
{
    # Return the lower 8 bits for octet 4
    printf '%d' $(( $1 & 0x00FF ))
}

function getCardCurrentAddr()
{
    ip -f inet addr show dev ${interface} | grep -o -P '([0-9.]+){4}(?=/)'
}

function main ()
{

    case "$1" in
        deconfig)
            log "$@ for ${interface}"
            #
            # special hack to make sure we get an ipv6 LL-Address
            if [[ $interface = "eth0" ]]; then
                ip addr flush dev $interface
                ip link set dev $interface down
            fi
            ;;

        leasefail)
            local addr_set=0
            #
            # derive third and fouth octets from serial number here
            #
            if [[ -x ${CARDSERIALNUMBERPROG} ]]; then
                cardSerialNumber=$( ${CARDSERIALNUMBERPROG} | tr -d "[:space:]")
            fi
            # make sure we have a "valid" value, even if seteeprom has failed...
            : ${cardSerialNumber:="SP300000"}

            local cardAddr=$( cardAddrFromSerialNumber ${cardSerialNumber#SP3})
            THIRD_OCTET=$(getOctet3FromCardAddr ${cardAddr})
            : ${THIRD_OCTET:="0"}
            #echo "THIRD_OCTET: ${THIRD_OCTET}"

            FOURTH_OCTET=$(getOctet4FromCardAddr ${cardAddr})
            : ${FOURTH_OCTET:="1"}
            #echo "FOURTH_OCTET: ${FOURTH_OCTET}"

            #
            # Make sure we haven't already set the APIPA address on a previous trip through the leasefail path.
            # If there's nothing to do, tell run-parts to give up, and thus NOT run the post-script, which otherwise would have signalled upnp_app to restart.
            # We also avoid spamming the syslog by this means.
            #
            local current_addr=$(getCardCurrentAddr)
            echo "current_addr: ${current_addr}"
            if [[ ${current_addr} == "${FIRST_AND_SECOND_OCTETS}.${THIRD_OCTET}.${FOURTH_OCTET}" ]]; then
                #log "$@ for ${interface}: current addr matches ${FIRST_AND_SECOND_OCTETS}.${THIRD_OCTET}.${FOURTH_OCTET}"
                exit 1
            fi
            if [[ ${current_addr} == "${FIRST_AND_SECOND_OCTETS}.${THIRD_OCTET}.$((FOURTH_OCTET + 1))" ]]; then
                #log "$@ for ${interface}: current addr matches ${FIRST_AND_SECOND_OCTETS}.${THIRD_OCTET}.$((FOURTH_OCTET + 1))"
                exit 1
            fi

            # No matching APIPA address currently, so set it now...
            log "leasefail for ${interface}: trying APIPA ${FIRST_AND_SECOND_OCTETS}.${THIRD_OCTET}.${FOURTH_OCTET} from SN ${cardSerialNumber}"
            (( addressesTried = 0 ))
            while (( ! $addr_set )) && (( addressesTried++ < 2 )) ; do
                CARDADDR="${FIRST_AND_SECOND_OCTETS}.${THIRD_OCTET}.${FOURTH_OCTET}"
                if [[ -x ${ARPINGPROG} ]] && ! ${ARPINGPROG} -D -c 2 -I ${interface} ${CARDADDR} ; then
                    log "address ${CARDADDR} apparently occupied on ${interface}"
                    (( ++FOURTH_OCTET ))
                else
                    log "setting address ${CARDADDR} on ${interface}"
                    ip -f inet addr flush dev ${interface}
                    ip addr add dev ${interface} local ${CARDADDR}/${NETMASK} ${BROADCAST} && addr_set=1
                fi
            done

            #
            # Last ditch: if we failed to set the address so far,
            # force it to (eth0) 169.254.0.1, or  (eth1) 169.254.0.3
            #
            if (( ! $addr_set )); then
                if [[ $interface = "eth1" ]]; then
                    CARDADDR="${FIRST_AND_SECOND_OCTETS}.0.3"
                else
                    CARDADDR="${FIRST_AND_SECOND_OCTETS}.0.1"
                fi
                log "forcing address to ${CARDADDR} on ${interface}"
                ip -f inet addr flush dev ${interface}
                ip addr add dev ${interface} local ${CARDADDR}/${NETMASK} ${BROADCAST} && addr_set=1
            fi
        ;;

    esac

    exit 0
}

main $*
