Home | History | Annotate | Line # | Download | only in dnssdutil
      1 #! /bin/bash
      2 #
      3 #	Copyright (c) 2020-2022 Apple Inc. All rights reserved.
      4 #
      5 
      6 declare -r version=1.1
      7 declare -r script=${BASH_SOURCE[0]}
      8 declare -r rcodesURL='https://www.iana.org/assignments/dns-parameters/dns-parameters-6.csv'
      9 
     10 #============================================================================================================================
     11 
     12 PrintHelp()
     13 {
     14 	echo ""
     15 	echo "Usage: $( basename "${script}" ) [options]"
     16 	echo ""
     17 	echo "Options:"
     18 	echo "    -h  Display script usage."
     19 	echo "    -V  Display version of this script and exit."
     20 	echo ""
     21 	echo "This script writes C functions to convert DNS RCODE values to strings and vice versa to stdout"
     22 	echo "based on the latest DNS RCODE data available at"
     23 	echo ""
     24 	echo "    ${rcodesURL}"
     25 	echo ""
     26 }
     27 
     28 #============================================================================================================================
     29 
     30 ErrQuit()
     31 {
     32 	echo "error: $*" 1>&2
     33 	exit 1
     34 }
     35 
     36 #============================================================================================================================
     37 
     38 StripLeadingTrailingWhitespace()
     39 {
     40 	sed 's/^[[:space:]]*//;s/[[:space:]]*$//'
     41 }
     42 
     43 #============================================================================================================================
     44 
     45 GetNamesAndValues()
     46 {
     47 	shopt -s nocasematch
     48 	while IFS=',' read value name others; do
     49 		name=$( StripLeadingTrailingWhitespace <<< "${name}" )
     50 		[[ ${name} =~ ^unassigned$ ]] && continue
     51 		
     52 		value=$( StripLeadingTrailingWhitespace <<< "${value}" )
     53 		[[ ${value} =~ ^[0-9]+$ ]] || continue
     54 		[ "${value}" -le 65535 ] || continue # Currently, RCODEs can be up to 16-bits in size.
     55 		
     56 		# The value 65535 is reserved according to <https://datatracker.ietf.org/doc/html/rfc6895#section-2.3>.
     57 		# However, the name in the CSV file is "Reserved, can be allocated by Standards Action". For simplicity, we just
     58 		# use the name "Reserved".
     59 		
     60 		if [ "${value}" -eq 65535 ]; then
     61 			name='Reserved'
     62 		fi
     63 		echo "${name},${value}"
     64 	done
     65 	shopt -u nocasematch
     66 }
     67 
     68 #============================================================================================================================
     69 
     70 RCodeMnemonicToEnum()
     71 {
     72 	name="${1//[^A-Za-z0-9_]/_}" # Only allow alphanumeric and underscore characters.
     73 	printf "kDNSRCode_${name}"
     74 }
     75 
     76 #============================================================================================================================
     77 
     78 PrintRCodeEnums()
     79 {
     80 	local -r inputFile=${1}
     81 	printf "typedef enum\n"
     82 	printf "{\n"
     83 	local sep=""
     84 	< "${inputFile}" sort --field-separator=, --key=2,2 --numeric-sort --unique |
     85 	while IFS=',' read name value; do
     86 		printf "%b" "${sep}"
     87 		local enum=$( RCodeMnemonicToEnum "${name}" )
     88 		printf "\t%-20s= %d" "${enum}" "${value}"
     89 		sep=",\n"
     90 	done
     91 	printf "\n"
     92 	printf "\t\n"
     93 	printf "}\tDNSRCode;\n"
     94 }
     95 
     96 #============================================================================================================================
     97 
     98 PrintValueToStringElseIf()
     99 {
    100 	local -r first=${1}
    101 	local -r last=${2}
    102 	[ "${first}" -le "${last}" ] || ErrQuit "${first} > ${last}"
    103 	shift 2
    104 	local stringArray=( "$@" )
    105 	
    106 	if [ "${last}" -ne "${first}" ]; then
    107 		printf "\telse if( ( inValue >= ${first} ) && ( inValue <= ${last} ) )\n"
    108 		local -r arrayVarName="sNames_${first}_${last}"
    109 	else
    110 		printf "\telse if( inValue == ${first} )\n"
    111 		local -r arrayVarName="sNames_${first}"
    112 	fi
    113 	printf "\t{\n"
    114 	printf "\t\tstatic const char * const\t\t${arrayVarName}[] =\n"
    115 	printf "\t\t{\n"
    116 	local value=${first}
    117 	for string in "${stringArray[@]}"; do
    118 		printf "\t\t\t%-15s // %3d\n" "\"${string}\"," "${value}"
    119 		value=$(( value + 1 ))
    120 	done
    121 	local -r stringCount=$(( value - first ))
    122 	local -r expectedCount=$(( last - first + 1 ))
    123 	[ "${stringCount}" -eq "${expectedCount}" ] || ErrQuit "${stringCount} != ${expectedCount}"
    124 	printf "\t\t};\n"
    125 	printf "\t\tstring = ${arrayVarName}[ inValue - ${first} ];\n"
    126 	printf "\t}\n"
    127 }
    128 
    129 #============================================================================================================================
    130 
    131 PrintValueToStringFunction()
    132 {
    133 	local -r inputFile=${1}
    134 	printf "const char *\tDNSRCodeToString( const int inValue )\n"
    135 	printf "{\n"
    136 	printf "\tswitch( inValue )\n"
    137 	printf "\t{\n"
    138 	< "${inputFile}" sort --field-separator=, --key=2,2 --numeric-sort --unique |
    139 	{
    140 		local stringArray=()
    141 		while IFS=',' read name value; do
    142 		    local enum=$( RCodeMnemonicToEnum "${name}" )
    143 			printf "\t\t%-28s%s\n" "case ${enum}:" "return( \"${name}\" );"
    144 		done
    145 	}
    146 	printf "\t\t%-28sreturn( NULL );\n" "default:"
    147 	printf "\t}\n"
    148 	printf "}\n"
    149 }
    150 
    151 #============================================================================================================================
    152 
    153 PrintStringToValueFunction()
    154 {
    155 	local -r inputFile=${1}
    156 	printf "#include <stdlib.h>\n"
    157 	printf "\n"
    158 	printf "typedef struct\n"
    159 	printf "{\n"
    160 	printf "\tconst char *\t\tname;\n"
    161 	printf "\tint\t\t\t\t\tvalue;\n"
    162 	printf "\t\n"
    163 	printf "}\t_DNSRCodeTableEntry;\n"
    164 	printf "\n"
    165 	printf "static int\t_DNSRCodeFromStringCmp( const void *inKey, const void *inElement );\n"
    166 	printf "\n"
    167 	printf "int\tDNSRCodeFromString( const char * const inString )\n"
    168 	printf "{\n"
    169 	printf "\t// The name-value table is sorted by name in ascending lexicographical order to allow going from name to\n"
    170 	printf "\t// value in logarithmic time via a binary search.\n"
    171 	printf "\t\n"
    172 	printf "\tstatic const _DNSRCodeTableEntry\t\tsTable[] =\n"
    173 	printf "\t{\n"
    174 	
    175 	local sep=""
    176 	< "${inputFile}" sort --field-separator=, --key=1,1 --ignore-case --unique |
    177 	while IFS=',' read name value; do
    178 		printf "%b" "${sep}"
    179 		local enum=$( RCodeMnemonicToEnum "${name}" )
    180 		printf "\t\t%-16s%-20s}" "{ \"${name}\"," "${enum}"
    181 		sep=",\n"
    182 	done
    183 	printf "\n"
    184 	printf "\t};\n"
    185 	printf "\tconst _DNSRCodeTableEntry *\t\t\tentry;\n"
    186 	printf "\t\n"
    187 	printf "\tentry = (_DNSRCodeTableEntry *) bsearch( inString, sTable, sizeof( sTable ) / sizeof( sTable[ 0 ] ),\n"
    188 	printf "\t\tsizeof( sTable[ 0 ] ), _DNSRCodeFromStringCmp );\n"
    189 	printf "\treturn( entry ? entry->value : -1 );\n"
    190 	printf "}\n"
    191 	printf "\n"
    192 	printf "static int\t_DNSRCodeFromStringCmp( const void * const inKey, const void * const inElement )\n"
    193 	printf "{\n"
    194 	printf "\tconst _DNSRCodeTableEntry * const\t\tentry = (const _DNSRCodeTableEntry *) inElement;\n"
    195 	printf "\treturn( strcasecmp( (const char *) inKey, entry->name ) );\n"
    196 	printf "}\n"
    197 }
    198 
    199 #============================================================================================================================
    200 
    201 ExitHandler()
    202 {
    203 	if [ -d "${tempDir}" ]; then
    204 		rm -fr "${tempDir}"
    205 	fi
    206 }
    207 
    208 #============================================================================================================================
    209 
    210 PrintAutoGenNote()
    211 {
    212 	printf "// This code was autogenerated on $( date -u '+%Y-%m-%d' ) by $( basename ${script} ) version ${version}\n"
    213 	printf "// Data source URL: ${rcodesURL}\n"
    214 	printf "\n"
    215 }
    216 
    217 #============================================================================================================================
    218 
    219 main()
    220 {
    221 	while getopts ":hO:V" option; do
    222 		case "${option}" in
    223 			h)
    224 				PrintHelp
    225 				exit 0
    226 				;;
    227 			V)
    228 				echo "$( basename "${script}" ) version ${version}"
    229 				exit 0
    230 				;;
    231 			:)
    232 				ErrQuit "option '${OPTARG}' requires an argument."
    233 				;;
    234 			*)
    235 				ErrQuit "unknown option '${OPTARG}'."
    236 				;;
    237 		esac
    238 	done
    239 	
    240 	[ "${OPTIND}" -gt "$#" ] || ErrQuit "unexpected argument \"${!OPTIND}\"."
    241 	
    242 	trap ExitHandler EXIT
    243 	tempDir=$( mktemp -d ) || ErrQuit "Failed to make temporary directory."
    244 	declare -r originalRCodesFile="${tempDir}/rcodesOriginal.csv"
    245 	curl --output "${originalRCodesFile}" "${rcodesURL}" || ErrQuit "Failed to download CSV file."
    246 	
    247 	declare -r rcodesFile="${tempDir}/rcodes.csv"
    248 	< "${originalRCodesFile}" GetNamesAndValues > "${rcodesFile}"
    249 	
    250 	declare -r tempFile="${tempDir}/temp.csv"
    251 	< "${rcodesFile}" sort --field-separator=, --key=2,2 --unique --numeric-sort > "${tempFile}"
    252 	< "${tempFile}" sort --field-separator=, --key=1,1 --unique --ignore-case > "${rcodesFile}"
    253 	
    254 	PrintAutoGenNote
    255 	PrintRCodeEnums "${rcodesFile}"
    256 	printf "\n"
    257 	PrintAutoGenNote
    258 	PrintValueToStringFunction "${rcodesFile}"
    259 	printf "\n"
    260 	PrintAutoGenNote
    261 	PrintStringToValueFunction "${rcodesFile}"
    262 }
    263 
    264 main "$@"
    265