#!/bin/bash -p
#To decode the output of rdsfp -d <port>  or sfp -d <port>  or DU/XMU/RU/RRU
#Based on SFF-8472 : Specification for Diagnostic Monitoring Interface for Optical Transceivers

moshelldir=`dirname "$0"`
if [[ $moshelldir != /* ]] ; then moshelldir=`pwd`/$moshelldir ; fi
vobsinstallation=0
unamea=$(uname -a)
gawkext=""
gawklib=""
if [[ $unamea = [Ll][iI][nN][uU][xX]*x86_64* ]] ; then gawklib="lin64"   ; if [[ $vobsinstallation = 1 ]] ; then gawkext=".lin64" ; fi
elif [[ $unamea = [Ll][iI][nN][uU][xX]* ]]      ; then gawklib="linux"   ; if [[ $vobsinstallation = 1 ]] ; then gawkext=".linux" ; fi
elif [[ $unamea = SunOS*sparc* ]]               ; then gawklib="solaris" ; if [[ $vobsinstallation = 1 ]] ; then gawkext=".solaris" ; fi
elif [[ $unamea = SunOS* ]]                     ; then gawklib="sol86"   ; if [[ $vobsinstallation = 1 ]] ; then gawkext=".sol86" ; fi
else gawklib="cygwin" ; gawkext=".exe"
fi
gawkprog="gawk${gawkext}"
gawk="$moshelldir/$gawkprog"
#special case where moshell has been installed on a linux 32 bit machine but should be run on linux 64 sharing the same file system
if [[ $unamea = [Ll][iI][nN][uU][xX]*x86_64* && $vobsinstallation != 1 && `file "$moshelldir/gawk"` = *32-bit* ]] ; then gawklib="linux" ; fi
filefunc="$moshelldir/commonjars/lib/${gawklib}/filefuncs"
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}$moshelldir/commonjars/lib/${gawklib}
export LANG=C
export LC_ALL=C

globmoshellrc=$moshelldir/jarxml/moshellrc
commonjardir=$moshelldir/commonjars
jarxmldir=$moshelldir/jarxml
defLogDir=$commonjardir/defLogDir.sh
moshellrc=$($defLogDir)


$gawk -f $moshelldir/funcs.awk -v moshelldir="$moshelldir" -v vobsinstallation="$vobsinstallation" -v globmoshellrc="$globmoshellrc" -v moshellrc="$moshellrc" -l "$filefunc" --source '
BEGIN{
	loadFilefuncs()
	parse_user_variables(moshell)
	parse_user_variables(globmoshellrc)
	parse_user_variables(moshellrc)
	if (java=="") java="java"
	if (os~"cygwin") { "cygpath -m "moshelldir | getline moshelldir ; close("cygpath -m "moshelldir) }
	#print java,moshelldir
	sfpdecoder=java" -cp "moshelldir"/commonjars/sfpdecoder.jar Main"
}
{
	if (!($0~/^====================+$/ || $2=="lhsh" || $0=="$ " || $1=="coli>/fruacc/lhsh")) print
	if ($1~/^[^: ]+:$/)
	{
		prefix=$1" "
		sub(/^[^: ]+: +/,"")
		$0=$0
	}
	#=============================================================================================
	#lhsh xxx sfp -d 0
	#lhsh xxx sfp -d 0 -m
	#coli>/fruacc/lhsh BXP_2 sfp -d 0
	if ($0~/^====================+$/ || $2=="lhsh" || $0=="$ " || $1=="coli>/fruacc/lhsh") 
	{ 
		if ($2=="lhsh" || $1=="coli>/fruacc/lhsh")
		{
			if ($NF=="-m") port=$(NF-1) 
			else port=$NF 
			tcommand=$0
		}
		if (buffer!="") { sdec(buffer) ; buffer="" }
		print
	}
	if (/^ *[0-9A-Fa-f]{4} +[0-9A-Fa-f]{8} +[0-9A-Fa-f]{8} +[0-9A-Fa-f]{8} +[0-9A-Fa-f]{8} +["]?................["]? *$/)
	{
		buffer=buffer"\n"$0
	}
	else if (tcommand ~ /\/sfpdump / && $0 ~ / \(C|V|mA|mW\): /)
	{
		#coli>/fruacc/lhsh 000100 /fruacc/du/sfpdump 0
		#Temperature           (C): 30.816
		#Vcc                   (V):  3.319
		#TX Power             (mW):  0.701
		#RX Power             (mW):  0.729
		#TX Bias              (mA): 40.314
		#TX Bias Low Warning  (mA): 20.000
		#TX Bias High Warning (mA): 80.000
		t_header=prefix "SFPDECODER ("port"): "
		if ($1=="Temperature") t_buff=t_header "SFP Temperature = "sprintf("%.0f C",$NF)
		else if ($1=="Vcc")    t_buff=t_buff"\n"t_header "SFP Voltage = "sprintf("%.2f Volts",$NF)
		else if ($2=="Power")  t_buff=t_buff"\n"t_header "SFP "$1" power = "sprintf("%.5f dBm",10*mylog($NF)/log(10))
		else if ($2=="Bias")
		{
			if ($3=="(mA):") 
			{
				t_bias=$NF
				t_buff=t_buff"\n"t_header "SFP TX Bias = "sprintf("%.1f mA",$NF)
			}
			else if ($3=="Low") t_low=$NF
			else if ($3=="High")
			{
				t_high=$NF
				t_buff=t_buff"\n"t_header "SFP TX Bias Level = "sprintf("%.0f%",mDiv(100 * (t_bias - t_low) , (t_high - t_low)))
				print t_buff
				t_buff=""
			}
		}
	}
}
END{ 
	if (buffer!="") sdec(buffer)
}
function sdec(input,  n,a,i,b,j,c,s,z,v,k,m,w,d,t,start,stop,area,byte,theVal,f,cal,r,r0,r1,r2,r3,r4)
{
	header=prefix "SFPDECODER ("port"): "
	idec()
	n=split(input,a,"\n")
	for (i=1;i<=n;i++)
	{
		w=split(a[i],b," ")
		if (w<5) continue
		s=h2d(b[1])+0
		if (s>=256) 
		{
			s-=256
			#On most RU/RRU , the byte numbering for A2 starts again at 0000 . 
			#But on RRUS31, the byte number for A2 starts at 0100 (= 256) so we remove 256 if s is greater than 256
		}
		#print s,a[i]
		if (s==0) z++
		#print z,s,b[2],b[3],b[4],b[5]
		t=b[2] b[3] b[4] b[5]
		v=0
		split(t,c,"")
		for (k=1;k<=32;k++)
		{
			byte[z,s+v]=c[k] c[k+1]
			v++
			k++
		}
	}
	#for (m in byte) { split(m,d,SUBSEP) ; printf("%3s %3s %s\n",d[1],d[2],byte[m]) }
	for (i=1;i<=Max;i++)
	{
		split(fPos[i],e,",")
		if (e[1]=="A0") area=1 ; else area=2
		start=e[2] ; stop=e[3]
		for (j=start;j<=stop;j++)
		{
			thebyte=byte[area,j]
			if (i<Max1)
			{
				if (byte[area,j]=="00" || byte[area,j]=="20") thebyte=""
				else thebyte=sprintf("%c",strtonum("0x"byte[area,j]))
			}
			else if (i==Max1) thebyte=h2d(byte[area,j])
			fVal[i]=fVal[i] thebyte
		}
		if (fName[i]=="sfpManufactureDate") fVal[i]=todate(fVal[i])
		else if (fName[i]=="EricssonProductRev") 
		{
			split(fVal[i],f,"")
			if (f[1]==0 || f[1]>31) fVal[i]=""
			else fVal[i]="R" f[1] int2char(f[2])
		}
		else if (i>Max1 && i<=Max3) fVal[i]=h2d(fVal[i])
		#print i" "fName[i]" "fVal[i]
	}
	if (fVal[1]=="" && fVal[2]=="" && fVal[3]=="" && fVal[4]=="" && fVal[5]=="") return
	for (i=1;i<=Max2;i++)
	{
		gsub(/\x0a1|\x001/,"",fVal[i])
		print header fName[i]" = "fVal[i]
	}
	#print byte[1,92],h2b(byte[1,92])
	split(h2b(byte[1,92]),a,"")  #internal cal : a[3] , external cal: a[4]
	if (a[3]==a[4]) 
	{
		print header "Erroneous value in byte 92 internalcal=externalcal"
		return
	}
	if (a[3]==1) cal="i" ; else cal="e"
	for (i=Max3+1;i<=Max;i++) fVal[fName[i]]=fVal[i]
	print header "Diagnostics data ("(cal=="i"?"internal":"external")" calibration)"
	for (i=Max2+1;i<=Max3;i++)
	{
		if (fName[i]=="SFP TX Bias")
		{
			if (cal=="i") fVal[i]=sprintf("%.1f mA",fVal[i]/500)
			else
			{
				#print r,r1,r2
				r=fVal[i] ; r1=h2p(fVal["ISlope"]) ; r2=h2s(fVal["IOffset"])
				fVal[i]=sprintf("%.1f mA",(r*r1+r2)/500)
			}
		}
		else if (fName[i]=="SFP TX Bias Level")
		{
			fVal[i]=sprintf("%.0f%",mDiv(100 * (fVal[i] - h2d(fVal["BiasLowWarning"])) , (h2d(fVal["BiasHighWarning"]) - h2d(fVal["BiasLowWarning"]))))
		}
		else if (fName[i]~"SFP .X power")
		{
			if (cal=="i")
			{
				fVal[i]=10*mylog(fVal[i]/10000)/log(10)" dBm"
			}
			else if (fName[i]=="SFP RX power")
			{
				r=fVal[i] ; r0=h2f(fVal["RxPwr0"]) ; r1=h2f(fVal["RxPwr1"]) ; r2=h2f(fVal["RxPwr2"]) ; r3=h2f(fVal["RxPwr3"]) ; r4=h2f(fVal["RxPwr4"])
				#print r,r0,r1,r2,r3,r4
				#print (r0+ r1*r + r2*(r^2) + r3*(r^3) + r4*(r^4)) 
				fVal[i]=10*mylog((r0+ r1*r + r2*(r^2) + r3*(r^3) + r4*(r^4))/10000)/log(10)" dBm"
			}
			else if (fName[i]=="SFP TX power")
			{
				r=fVal[i] ; r1=h2p(fVal["TxPwrSlope"]) ; r2=h2s(fVal["TxPwrOffset"])
				#print r,r1,r2,h2p(r1),h2s("ffff"),h2s("7fff")
				fVal[i]=10*mylog((r*r1+r2)/10000)/log(10)" dBm"
			}
		}
		else if (fName[i]=="SFP Temperature")
		{
			#fVal[1] contains the sfpVendorName . For OCLARO (OPNEXT), there is a issue with temperature calibration values so we always consider internal calibration
			if (cal=="i" || fVal[1]~/^(OPNEXT|OCLARO|AOI)/) fVal[i]=sprintf("%.0f",fVal[i]/256)
			else
			{
				r=fVal[i] ; r1=h2p(fVal["TSlope"]) ; r2=h2s(fVal["TOffset"])
				#print r,fVal["TSlope"],r1,fVal["TOffset"],r2
				fVal[i]=sprintf("%.0f",(r*r1+r2)/256)
			}
			#Now do the unsigned to signed conversion
			if ((fVal[i]+0) >= 128) fVal[i]-=256
			fVal[i]=fVal[i]" C"
		}
		else if (fName[i]=="SFP Voltage")
		{
			if (cal=="i") fVal[i]=sprintf("%.2f Volts",fVal[i]/10000)
			else
			{
				r=fVal[i] ; r1=h2p(fVal["VSlope"]) ; r2=h2s(fVal["VOffset"])
				#print r,r1,r2
				fVal[i]=sprintf("%.2f Volts",(r*r1+r2)/10000)
			}
		}
		print header fName[i]" = "fVal[i]
	}
}
function h2b(h,   a,h2b_table,res)
{
	h2b_table["0"]="0000"
	h2b_table["1"]="0001"
	h2b_table["2"]="0010"
	h2b_table["3"]="0011"
	h2b_table["4"]="0100"
	h2b_table["5"]="0101"
	h2b_table["6"]="0110"
	h2b_table["7"]="0111"
	h2b_table["8"]="1000"
	h2b_table["9"]="1001"
	h2b_table["A"]="1010"
	h2b_table["B"]="1011"
	h2b_table["C"]="1100"
	h2b_table["D"]="1101"
	h2b_table["E"]="1110"
	h2b_table["F"]="1111" 
	h=toupper(h)
	z=split(h,a,"")
	res=""
	for(i=1;i<=z;i++) res=res h2b_table[a[i]]
	return res
}
function todate(h,   a,n)
{
	n=split(h,a,"")
	if (n==6) h="20"a[1] a[2]"-"a[3] a[4]"-"a[5] a[6]
	return h
}	
function h2d(h)
{
	return sprintf("%d",strtonum("0x"h))
}
function idec(    ref,i,a)
{
	#initialisation, done for each individual A0+A2 buffer
	delete ref
	delete fName
	delete fPos
	delete fVal
	Max=0
	Max1=0
	#fist ASCII fields until Max1 (except the Ericsson product rev)
	ref[++Max]="sfpVendorName=A0,20,35"
	ref[++Max]="sfpProductNumber=A0,40,55"
	ref[++Max]="sfpProductRevision=A0,56,59"
	ref[++Max]="sfpSerialNumber=A0,68,83"
	ref[++Max]="sfpManufactureDate=A0,84,89"
	ref[++Max]="Logo=A0,96,99"
	ref[++Max]="EricssonProductNrPart1=A0,119,127"
	ref[++Max]="EricssonProductNrPart2=A0,112,117"
	ref[++Max]="EricssonProductRev=A0,118,119"  #Important: the EricssonProductRev must always be in position "Max1"
	Max1=Max   #the number of ascii fields
	#then decimal values until Max3
	ref[++Max]="sfpBitRate (units of 100MBd)=A0,12,12"
	ref[++Max]="sfpWaveLength(nm)=A0,60,61"
	ref[++Max]="sfpSingleModeMaxLength(1Km)=A0,14,14"
	ref[++Max]="sfpSingleModeMaxLength(100m)=A0,15,15"
	ref[++Max]="sfpLinkLengthSupported(50UM-OM2, 10m)=A0,16,16"
	ref[++Max]="sfpLinkLengthSupported(62.5UM-OM1, 10m)=A0,17,17"
	ref[++Max]="sfpLinkLengthSupported(Active Cable or Copper, 1m)=A0,18,18"
	ref[++Max]="sfpLinkLengthSupported(50UM-OM3, 10m)=A0,19,19"
	Max2=Max
	#start diagnostics at Max2+1
	ref[++Max]="SFP Temperature=A2,96,97"
	ref[++Max]="SFP Voltage=A2,98,99"
	ref[++Max]="SFP TX Bias=A2,100,101"
	ref[++Max]="SFP TX Bias Level=A2,100,101"
	ref[++Max]="SFP TX power=A2,102,103"
	ref[++Max]="SFP RX power=A2,104,105"
	Max3=Max
	#additional values needed for diagnostics calculations, starts at Max3+1, not printed, and kept as hexadecimal
	ref[++Max]="BiasLowWarning=A2,22,23"
	ref[++Max]="BiasHighWarning=A2,20,21"
	ref[++Max]="RxPwr4=A2,56,59"
	ref[++Max]="RxPwr3=A2,60,63"
	ref[++Max]="RxPwr2=A2,64,67"
	ref[++Max]="RxPwr1=A2,68,71"
	ref[++Max]="RxPwr0=A2,72,75"
	ref[++Max]="TxPwrSlope=A2,80,81"
	ref[++Max]="TxPwrOffset=A2,82,83"
	ref[++Max]="TSlope=A2,84,85"
	ref[++Max]="TOffset=A2,86,87"
	ref[++Max]="VSlope=A2,88,89"
	ref[++Max]="VOffset=A2,90,91"
	ref[++Max]="ISlope=A2,76,77"
	ref[++Max]="IOffset=A2,78,79"
	
	for (i=1;i<=Max;i++)
	{
		split(ref[i],a,"=")
		fName[i]=a[1]
		fPos[i]=a[2]
		#print i" "fName[i],fPos[i]
	}
}
function int2char(i,   char)
{
	char["1"]="A"
	char["2"]="B"
	char["3"]="C"
	char["4"]="D"
	char["5"]="E"
	char["6"]="F"
	char["7"]="G"
	char["8"]="H"
	char["9"]="I"
	char["10"]="J"
	char["11"]="K"
	char["12"]="L"
	char["13"]="M"
	char["14"]="N"
	char["15"]="O"
	char["16"]="P"
	char["17"]="Q"
	char["18"]="R"
	char["19"]="S"
	char["20"]="T"
	char["21"]="U"
	char["22"]="V"
	char["23"]="W"
	char["24"]="X"
	char["25"]="Y"
	char["26"]="Z"
	char["27"]="AA"
	char["28"]="AB"
	char["29"]="AC"
	char["30"]="AD"
	char["31"]="AE"
	return char[i]
}
function h2s(h,   a,v,b2h_table,sign)
{
	#16bit hex to signed integer. The first bit is the sign, the next 15 bits are the value
	b2h_table["0000"]="0"
	b2h_table["0001"]="1"
	b2h_table["0010"]="2"
	b2h_table["0011"]="3"
	b2h_table["0100"]="4"
	b2h_table["0101"]="5"
	b2h_table["0110"]="6"
	b2h_table["0111"]="7"
	b2h_table["1000"]="8"
	b2h_table["1001"]="9"
	b2h_table["1010"]="A"
	b2h_table["1011"]="B"
	b2h_table["1100"]="C"
	b2h_table["1101"]="D"
	b2h_table["1110"]="E"
	b2h_table["1111"]="F" 
	split(h,a,"")
	split(h2b(a[1]),b,"")
	if (b[1]=="1") sign="-"
	a[1]=b2h_table["0"b[2] b[3] b[4]]
	h=a[1] a[2] a[3] a[4]
	return sprintf("%s%d",sign,strtonum("0x"h))
}
function h2p(h,   a,b1,b2)
{
	#16bit hex to fixed point: the first byte is 0 to 255, the second byte is the decimal part 0 to 255/256  
	split(h,a,"")
	b1=a[1] a[2]
	b2=a[3] a[4]
	b1=sprintf("%f",strtonum("0x"b1))
	b2=sprintf("%f",strtonum("0x"b2)/256)
	return b1+b2
}
function h2f(h,   a,b0,b1,b2,b3)
{
	#convert a raw 4 byte hexadecimal to single precision float 
	#can also be done with perl:
	#perl -e \x27 my $hex = "be88a84d";print unpack "f", reverse pack "H*", $hex;\x27
	split(h,a,"")
	b3=a[1] a[2] ; b2=a[3] a[4] ; b1=a[5] a[6] ; b0=a[7] a[8]
	return HexToFP(b3,b2,b1,b0)
}
# function to convert 4 "bytes" of hex chars into a floating point
# bytes are ordered
# [high....low]
#  b3 b2 b1 b0
# based on http://ecpe.ece.iastate.edu/arun/CprE281_F05/ieee754/ie5.html
# http://babbage.cs.qc.edu/IEEE-754/Decimal.html was used to test my code
# 
function HexToFP(b3,b2,b1,b0,   i0,i1,i2,i3,tmp,exponent,fraction,decfrac,fval,bias,sign,i)
{
	fval = 0
	bias=127
	
	# convert the character bytes into numbers
	i3=strtonum("0x" b3)
	i2=strtonum("0x" b2)
	i1=strtonum("0x" b1)
	i0=strtonum("0x" b0)
	
	# first, we need to separate all the parts of the floating point
	
	# upper most bit is the sign
	sign=and(i3, 0x80)
	
	# bits 23-30 (next 8) are the exponent
	exponent=and(i3,0x7F)
	tmp=and(i2,0x80)
	tmp=rshift(tmp, 7)
	exponent=lshift(exponent, 1)			
	exponent=or(exponent,tmp)	
	
	# bits 0-22 are the fraction
	fraction=and(i2,0x7F)
	fraction=lshift(fraction,8)
	fraction=or(fraction,i1)
	fraction=lshift(fraction,8)
	fraction=or(fraction,i0)
	
	#convert the fraction part into a decimal. need to look at the 
	# individual bits and compute the base 10 value
	decfrac=0
	for(i=22;i>=0;i--)
	{			
		if( and( fraction , lshift(0x01,i) ))
			decfrac = decfrac+2^(i-23)
	}

	fval=(1+decfrac) * 2^(exponent-bias)
	
	if(sign)
		fval=fval*-1
		
	return fval 
}
function mylog(theval)
{
	if (theval+0 < 0) theval=0
	return log(theval)
}
'
