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