dns-rcode-func-autogen revision 1.1 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