#!/bin/bash
#
#
#

#################################################
#
# Global constants
#
#################################################
EEPROM_SIZE="32768"	# 24LC256: 32kByte (256kbit)
EEPROM_RAYLASE_SIZE="1024" # i.e. only the first 1024 bytes (max) are occupied by our stuff...
RAYLASE_ETHADDR_PREFIX="00:22:4A"

MAGIC_KEY="MAGIC"
MAGIC_VALUE="RAYLASE AG SP-ICE-3"

EEPROM_FORMAT_KEY="EEPROM_FORMAT"
EEPROM_FORMAT_VALUE="01"

SN_KEY="SN"
SN_VALUE="SP300001"

ETH0ADDR_KEY="ETH0ADDR"
ETH0ADDR_VALUE="${RAYLASE_ETHADDR_PREFIX}:01:00:01"

ETH1ADDR_KEY="ETH1ADDR"
ETH1ADDR_VALUE="${RAYLASE_ETHADDR_PREFIX}:01:00:00"

IPV4ADDR0_KEY="IPV4ADDR0"
IPV4ADDR0_VALUE="DHCP"

IPV4ADDR1_KEY="IPV4ADDR1"
IPV4ADDR1_VALUE="DHCP"

IPV4NETMASK0_KEY="IPV4NETMASK0"
IPV4NETMASK0_VALUE="255.255.0.0"

IPV4NETMASK1_KEY="IPV4NETMASK1"
IPV4NETMASK1_VALUE="255.255.0.0"

EEPROM_MAGIC="${MAGIC_KEY}=${MAGIC_VALUE}"

EEPROM_DEVICE_PATHNAME="/sys/bus/i2c/devices/0-0050/eeprom"

CMDNAME="${0##*/}"

#
# this is used for simulating the eeprom
#
EEPROM_SIMULATION_TXT="/tmp/eeprom-simulation.txt"


#################################################
#
# Global variables
#
#################################################

#
# This is a local cache to avoid having to read the eeprom more than once
# during any particular invocation of this script.
# It consists of an associative array, to make keyvalue manipulation easier,
# and a list of the invariant keys in the "correct" order.
#
declare -A EEPROM_CONTENTS_ARRAY
declare -a EEPROM_CONTENTS_KEY_ORDER
declare EEPROM_CONTENTS_DIRTY=0


#################################################
#
# Helper routines
#
#################################################

function Usage()
{
	cat <<-ENDUSAGE
		USAGE:
		  ${CMDNAME} [options] [key[=[value]]] - get/delete/set KEY-VALUE string pair(s) in the EEPROM

		OPTIONS:
		 -i    - by itself: initialises EEPROM using standard values for SN and ETHxADDR;
			   - with "SN=SP3nnnnn": initialises EEPROM with given SN and generated ETHxADDR values.

		 -f    - factory-reset of EEPROM contents.

		 -c    - clear EEPROM completely

		 -v    - be verbose
		 -x    - enable execution trace ("set -x")
		 -s    - simulate EEPROM using '${EEPROM_SIMULATION_TXT}'

		EXAMPLES:
		  ${CMDNAME}                - get values of all currently defined keys
		  ${CMDNAME} key            - get value of key (if available)
		  ${CMDNAME} key=           - delete key (and its value)
		  ${CMDNAME} key=abc        - set value of key to "abc" (overwriting any previous value)
		  ${CMDNAME} key="abc def"  - set value of key to "abc def" (ditto)

		NOTES:
		  There must be no whitespace between key, the "=" sign, and value.
		  Whitespace is allowed _within_ the value, if value is quoted.

		  If a "-c" or "-i" option is specified, all non-option arguments are IGNORED.

	ENDUSAGE
}


function Say()
{
	printf "${CMDNAME}: %s() %s\n" "$1" "$2"
}

function Carp()
{
	printf "${CMDNAME}: arrrgh! %s() %s\n" "$1" "$2"
}


function Die()
{
	printf "${CMDNAME}: died of: %s\n" "${1:-'something awful'}." >&2
	printf "call stack:\n" >&2
	for (( i=0; i < ${#FUNCNAME[@]}; ++i )) do
		caller $i
	done >&2
	exit 1
}


function CheckEepromDevice()
{
	if [ -n "${SIMULATE}" ]; then
		return
	fi

	[ -w "${EEPROM_DEVICE_PATHNAME}" ] || Die "eeprom driver not instantiated"

}


#################################################
#
# Write EEPROM and related routines
#
#################################################

function WriteToEeprom() # text (if any) to write in ARG1
{
	local text=$1
	if (( ${#text} >= EEPROM_RAYLASE_SIZE  )); then
		Die "too much data for RAYLASE eeprom segment"
	fi

	# Pad out the text to fill our segment completely
	local padding="$(printf '\\xFF%.0s' $(seq 1 $(( EEPROM_RAYLASE_SIZE - ${#text} ))) )"

	local eeprom="${EEPROM_DEVICE_PATHNAME}"
	if [ -n "${SIMULATE}" ]; then
		eeprom="${EEPROM_SIMULATION_TXT}"
	else
		CheckEepromDevice
	fi

	printf "${text}${padding}" > ${eeprom}
}


function StoreEepromContents()
{
	[ -n "${!EEPROM_CONTENTS_ARRAY[*]}" ] || Die "lack of eeprom contents"
	[ -n "${EEPROM_CONTENTS_KEY_ORDER[*]}" ] || Die "don't know order in which to write eeprom contents"
	local text=""
	local t=""
	for k in "${EEPROM_CONTENTS_KEY_ORDER[@]}" ; do
		printf -v t "%s=%s\n" "$k" "${EEPROM_CONTENTS_ARRAY[$k]}"
		text="${text}$t"
	done

	WriteToEeprom "${text}"

	[ -n "${VERBOSE}" ] && Say "${FUNCNAME}" "done."
}

function ClearEepromContents()
{
	unset EEPROM_CONTENTS_ARRAY
	unset EEPROM_CONTENTS_KEY_ORDER
	rm -f ~/${EEPROM_SIMULATION_TXT}

	WriteToEeprom ""

	[ -n "${VERBOSE}" ] && Say "${FUNCNAME}" "done."
}

#
# This may be called with an optional serial number specification.
# If so, the SN is used to generate ETH0ADDR_VALUEs in place of the defaults.
#
function InitEepromContents()
{
	local keyvalue="$*"
	[ -n "${VERBOSE}" ] && echo "${FUNCNAME}: keyvalue='${keyvalue}'"
	local key
	local value
	IFS== read -r key value <<<"${keyvalue}"

	if [ "${key}" != "${keyvalue}" ] && [ "${key}" == "${SN_KEY}" ] ; then
		# have at least "SN="
		local value=${keyvalue#${key}=}

		if [ -n "${value}" ] && [ "${value}" ] ; then
			# set (new) value for key
			local sn=$(( 10#${value#SP3} ))
			if (( sn > 99999 )); then
				Die "bad serial number."
			fi
			local snaddr=$(( 65536 + ( (sn-1) * 5 ) ))
			local eth0suffix=$(printf "%02X:%02X:%02X" $(( (snaddr+1) / 65536 )) $(( ((snaddr+1) % 65536) / 256 )) $(( ((snaddr+1) % 65536) % 256 )) )
			local eth1suffix=$(printf "%02X:%02X:%02X" $(( (snaddr+2) / 65536 )) $(( ((snaddr+2) % 65536) / 256 )) $(( ((snaddr+2) % 65536) % 256 )) )
			ETH0ADDR_VALUE="${RAYLASE_ETHADDR_PREFIX}:${eth0suffix}"
			ETH1ADDR_VALUE="${RAYLASE_ETHADDR_PREFIX}:${eth1suffix}"
			SN_VALUE="${value}"
		fi
	fi
	declare -A EEPROM_CONTENTS_ARRAY
	EEPROM_CONTENTS_KEY_ORDER=(${MAGIC_KEY} ${EEPROM_FORMAT_KEY} ${SN_KEY} ${ETH0ADDR_KEY} ${ETH1ADDR_KEY} ${IPV4ADDR0_KEY} ${IPV4NETMASK0_KEY} ${IPV4ADDR1_KEY} ${IPV4NETMASK1_KEY})
	EEPROM_CONTENTS_ARRAY[$MAGIC_KEY]="${MAGIC_VALUE}"
	EEPROM_CONTENTS_ARRAY[$EEPROM_FORMAT_KEY]="${EEPROM_FORMAT_VALUE}"
	EEPROM_CONTENTS_ARRAY[$SN_KEY]="${SN_VALUE}"
	EEPROM_CONTENTS_ARRAY[$ETH0ADDR_KEY]="${ETH0ADDR_VALUE}"
	EEPROM_CONTENTS_ARRAY[$ETH1ADDR_KEY]="${ETH1ADDR_VALUE}"
	EEPROM_CONTENTS_ARRAY[$IPV4ADDR0_KEY]="${IPV4ADDR0_VALUE}"
	EEPROM_CONTENTS_ARRAY[$IPV4ADDR1_KEY]="${IPV4ADDR1_VALUE}"
	EEPROM_CONTENTS_ARRAY[$IPV4NETMASK0_KEY]="${IPV4NETMASK0_VALUE}"
	EEPROM_CONTENTS_ARRAY[$IPV4NETMASK1_KEY]="${IPV4NETMASK1_VALUE}"

	StoreEepromContents

	[ -n "${VERBOSE}" ] && DisplayEepromContents

}

#
# Dike out anything we didn't put in at the factory, reset the IPv4 addrs/masks, use existing SN to regenerate the ETHxADDR_VALUEs.
#
function FactoryResetEepromContents()
{
	LoadEepromContents

	#
	# re-calculate the ethX addresses from the existing serial number:
	#
	local snstr="${EEPROM_CONTENTS_ARRAY[$SN_KEY]}"
	local sn=$(( 10#${snstr#SP3} ))
	local snaddr=$(( 65536 + ( (sn-1) * 5 ) ))
	local eth0suffix=$(printf "%02X:%02X:%02X" $(( (snaddr+1) / 65536 )) $(( ((snaddr+1) % 65536) / 256 )) $(( ((snaddr+1) % 65536) % 256 )) )
	local eth1suffix=$(printf "%02X:%02X:%02X" $(( (snaddr+2) / 65536 )) $(( ((snaddr+2) % 65536) / 256 )) $(( ((snaddr+2) % 65536) % 256 )) )
	local eth0addr_value="${RAYLASE_ETHADDR_PREFIX}:${eth0suffix}"
	local eth1addr_value="${RAYLASE_ETHADDR_PREFIX}:${eth1suffix}"

	EEPROM_CONTENTS_ARRAY[$ETH0ADDR_KEY]="${eth0addr_value}"
	EEPROM_CONTENTS_ARRAY[$ETH1ADDR_KEY]="${eth1addr_value}"

	#
	# reset IPv4 addresses and masks to default values:
	#
	EEPROM_CONTENTS_ARRAY[$IPV4ADDR0_KEY]="${IPV4ADDR0_VALUE}"
	EEPROM_CONTENTS_ARRAY[$IPV4ADDR1_KEY]="${IPV4ADDR1_VALUE}"
	EEPROM_CONTENTS_ARRAY[$IPV4NETMASK0_KEY]="${IPV4NETMASK0_VALUE}"
	EEPROM_CONTENTS_ARRAY[$IPV4NETMASK1_KEY]="${IPV4NETMASK1_VALUE}"

	#
	# only keep the stuff we know about: discard everything else.
	#
	EEPROM_CONTENTS_KEY_ORDER=(${MAGIC_KEY} ${EEPROM_FORMAT_KEY} ${SN_KEY} ${ETH0ADDR_KEY} ${ETH1ADDR_KEY} ${IPV4ADDR0_KEY} ${IPV4NETMASK0_KEY} ${IPV4ADDR1_KEY} ${IPV4NETMASK1_KEY})

	StoreEepromContents

}

#################################################
#
# Read EEPROM and related routines
#
#################################################

#
# passes result back via ARG1;
# num lines to read is in ARG2, if present
# mum lines to skip is in ARG3, if present
#
function ReadFromEeprom()
{
	[ -n "$1" ] ||	Die "no result var name passed"

	local __resultVarName="$1"
	local numlines=${2:-0}
	local numskip=${3:-0}

	local eeprom="${EEPROM_DEVICE_PATHNAME}"
	if [ -n "${SIMULATE}" ]; then
		eeprom="${EEPROM_SIMULATION_TXT}"
	else
		CheckEepromDevice
	fi

	declare -a lines
	readarray -n ${numlines} -s ${numskip} -t lines < ${eeprom}

	local text=""
	for (( i=0; i < ${#lines[@]}; ++i )) do
		if [[ "${lines[i]}" == *=* ]]; then
			text="${text}${lines[i]}"$'\n'
		fi
	done

	[ -n "${VERBOSE}" ] && {
		echo "${FUNCNAME}: text..."
		echo "${text}"
	}

	eval $__resultVarName="'${text}'"

	[ -n "${VERBOSE}" ] && Say "${FUNCNAME}" "done."

}


CheckEepromMagic()
{
	local eepromtext=""
	ReadFromEeprom eepromtext 1
	local eeprom_magic="${eepromtext:0:${#EEPROM_MAGIC}}"
	[ "${EEPROM_MAGIC}" == "${eeprom_magic}" ] || {
		Carp "${FUNCNAME}" "expected: '${EEPROM_MAGIC}', got: '${eeprom_magic}'."
		Die "bad karma"
	}
}


function LoadEepromContents()
{
	CheckEepromMagic

	local eepromtext=""
	ReadFromEeprom eepromtext
	local key=""
	local value=""
	local n="0"
	while IFS== read -r key value; do
		[ -n "${key}" ] && [ -n "${value}" ] && {
			EEPROM_CONTENTS_KEY_ORDER[$((n++))]="${key}"
			EEPROM_CONTENTS_ARRAY[${key}]="${value}"
		}
	done <<<"${eepromtext}"

	[ -n "${VERBOSE}" ] && Say "${FUNCNAME}" "done."
}


function DisplayEepromContents()
{
	LoadEepromContents
	for k in "${EEPROM_CONTENTS_KEY_ORDER[@]}" ; do
		[ -n "$k" ] && printf "%s=%s\n" "$k" "${EEPROM_CONTENTS_ARRAY[$k]}"
	done
}


#################################################
#
# Key-Value routines
#
#################################################

function DisplayKeyValue()
{
	local key="$1"
	[ -n "${VERBOSE}" ] && echo "${FUNCNAME}:'${key}'"
	# check entry for key, and if found return value
	[ ${EEPROM_CONTENTS_ARRAY[$key]+_} ] && printf "%s\n" "${EEPROM_CONTENTS_ARRAY[$key]}"
}


function SetKeyValue()
{
	local key="$1"
	local value="$2"
	[ -n "${VERBOSE}" ] && echo "${FUNCNAME}:'${key}' '${value}'"

	# if the key is new, we need to add it to the KEYORDER array
	[ ${EEPROM_CONTENTS_ARRAY[$key]+_} ] || EEPROM_CONTENTS_KEY_ORDER[${#EEPROM_CONTENTS_KEY_ORDER[@]}]="${key}"

	EEPROM_CONTENTS_ARRAY[$key]="$value"

	[ -n "${VERBOSE}" ] && Say "${FUNCNAME}" "done."
}


function DeleteKeyValue()
{
	local key="$1"
	[ -n "${VERBOSE}" ] && echo "${FUNCNAME}:'${key}'"
	# if the element exists, remove it from the KEY_ORDER array
	if [ ${EEPROM_CONTENTS_ARRAY[$key]+_} ]; then
		for (( i=0; i<${#EEPROM_CONTENTS_KEY_ORDER[@]}; ++i )) do
			if [ "${EEPROM_CONTENTS_KEY_ORDER[$i]}" == "${key}" ]; then
				# remove the element completely (don't just unset it)...
				EEPROM_CONTENTS_KEY_ORDER=(${EEPROM_CONTENTS_KEY_ORDER[@]:0:$i} ${EEPROM_CONTENTS_KEY_ORDER[@]:$((i +1))})
				break
			fi
		done
	fi
	unset EEPROM_CONTENTS_ARRAY[${key}]

	[ -n "${VERBOSE}" ] && Say "${FUNCNAME}" "done."
}


function DoKeyValueAction()
{

	local keyvalue="$*"
	[ -n "${VERBOSE}" ] && echo "${FUNCNAME}: keyvalue='${keyvalue}'"
	local key
	local value
	IFS== read -r key value <<<"${keyvalue}"

	if [ "${key}" == "${keyvalue}" ] ; then
		#only have a key, so fetch and display it's current value
		DisplayKeyValue "${key}"
	else
		# have at least "key="
		local value=${keyvalue#${key}=}

		if [ -z "${value}" ] ; then
			# empty value: delete key
			DeleteKeyValue "${key}"
		else
			# set (new) value for key
			SetKeyValue "${key}" "${value}"
		fi

		EEPROM_CONTENTS_DIRTY=1

	fi
}


#################################################
#
# Main routine
#
#################################################
VERBOSE=""
SIMULATE=""
while getopts ":cfisvx" OPT
do
	#echo "${CMDNAME}: opt = '$OPT'"
	case $OPT in
		'c')
			ClearEepromContents
			exit 0
		;;

		'i')
			shift $((OPTIND-1))
			InitEepromContents "$@"
			exit 0
		;;

		'f')
			FactoryResetEepromContents
			exit 0
		;;

		'x')
			set -x
		;;

		's' )
			SIMULATE=1
		;;

		'v' )
			VERBOSE=1
		;;

		'?' )
			Usage
			exit 1
		;;

	esac
done

#
# move past the option(s), if any, to retrieve the remaining arguments
#
shift $((OPTIND-1))

LoadEepromContents

#
#in the absence of any non-option args at all, just dump the current contents of the eeprom
#
#echo "${CMDNAME}: optind= ${OPTIND}, num args = $#"
if [ $# -lt 1 ]; then
	DisplayEepromContents
	exit 0
fi

#
# otherwise, loop through the remaining arguments looking for stuff to do...
#
for a do
	DoKeyValueAction "$a"
done

if (( EEPROM_CONTENTS_DIRTY == 1 )); then
	StoreEepromContents
fi
