#!/bin/bash
#http://www.linuxquestions.org/questions/programming-9/bash-cidr-calculator-646701/

checkusage() {
    required_args="$1"
    shift

    if [ "$#" != $required_args ] ; then
        usage
    fi
    if [ "$1" = --help ] ; then
        usage
    fi
}

usage() {
    cat >&2 <<EOF
Function calculates various interchangable network parameters:
mask2cidr 255.255.0.0                    -> 16
cidr2mask 16                             -> 255.255.0.0
netcalc 192.168.1.2 <255.255.255.0|24>   -> 192.168.1.0
bcastcalc 192.168.1.2 <255.255.255.0|24> -> 192.168.1.255
EOF
    exit
}

mask2cidr() {
    local nbits dec
    local -a octets=( [255]=8 [254]=7 [252]=6 [248]=5 [240]=4
        [224]=3 [192]=2 [128]=1 [0]='0' )

    checkusage 1 "$@"

    while read -rd '.' dec; do
        [[ -z ${octets[dec]} ]] && echo "Error: $dec is not recognised" && exit 1
        (( nbits += octets[dec] ))
        (( dec < 255 )) && break
    done <<<"$1."

    echo "$nbits"
}

cidr2mask() {
    local i mask=""

    checkusage 1 "$@"

    local full_octets=$(($1/8))
    local partial_octet=$(($1%8))
    for ((i=0;i<4;i+=1)); do
        if [ $i -lt $full_octets ]; then
            mask+=255
        elif [ $i -eq $full_octets ]; then
            mask+=$((256 - 2**(8-$partial_octet)))
        else
            mask+=0
        fi
        test $i -lt 3 && mask+=.
    done

    echo $mask
}

netcalc(){
    checkusage 2 "$@"

    local addr="$1"
    local mask="$2"
    if ! [[ "$mask" = *.*.*.* ]] ; then
        mask=$(cidr2mask "$mask")
    fi

    local IFS='.' ip i
    local -a oct msk

    read -ra oct <<<"$addr"
    read -ra msk <<<"$mask"

    for i in ${!oct[@]}; do
        ip[i]="$(( oct[i] & msk[i] ))"
    done

    echo "${ip[*]}"
}

bcastcalc(){
    checkusage 2 "$@"

    local addr="$1"
    local mask="$2"
    if ! [[ "$mask" = *.*.*.* ]] ; then
        mask=$(cidr2mask "$mask")
    fi

    local IFS='.' ip i
    local -a oct msk

    read -ra oct <<<"$addr"
    read -ra msk <<<"$mask"

    for i in ${!oct[@]}; do
        ip[i]="$(( oct[i] + ( 255 - ( oct[i] | msk[i] ) ) ))"
    done

    echo "${ip[*]}"
}

case "${0##*/}" in
    mask2cidr|cidr2mask|netcalc|bcastcalc)
        "${0##*/}" "$@"
        ;;
    *)
        echo 'ERROR!' >&2
        exit 1
        ;;
esac

