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