#! /bin/bash # # Copyright (c) 2019-2020 Apple Inc. All rights reserved. # declare -r version=1.4 declare -r script=${BASH_SOURCE[0]} declare -r recordTypesURL='https://www.iana.org/assignments/dns-parameters/dns-parameters-4.csv' #============================================================================================================================ PrintHelp() { echo "" echo "Usage: $( basename "${script}" ) [options]" echo "" echo "Options:" echo " -O ',' Specifies a record name-value pair override. Can be used more than once." echo " -h Display script usage." echo " -V Display version of this script and exit." echo "" echo "This script writes C functions to convert DNS resource record type values to strings and vice versa to stdout" echo "based on the latest DNS resource record type data available at" echo "" echo " ${recordTypesURL}" echo "" } #============================================================================================================================ ErrQuit() { echo "error: $*" 1>&2 exit 1 } #============================================================================================================================ StripLeadingTrailingWhitespace() { sed 's/^[[:space:]]*//;s/[[:space:]]*$//' } #============================================================================================================================ GetNamesAndValues() { local -r isOverride=${1} shopt -s nocasematch while IFS=',' read name value others; do name=$( StripLeadingTrailingWhitespace <<< "${name}" ) [[ ${name} =~ ^unassigned$ ]] && continue value=$( StripLeadingTrailingWhitespace <<< "${value}" ) [[ ${value} =~ ^[0-9]+$ ]] || continue [ "${value}" -le 65535 ] || continue if [ "${value}" -eq 255 ]; then name=ANY fi echo "${name},${value},${isOverride}" done shopt -u nocasematch } #============================================================================================================================ RecordTypeMnemonicToEnum() { name="${1//[^A-Za-z0-9_]/_}" # Only allow alphanumeric and underscore characters. printf "kDNSRecordType_${name}" } PrintRecordTypesEnum() { local -r inputFile=${1} printf "typedef enum\n" printf "{\n" < "${inputFile}" sort --field-separator=, --key=2,2 --numeric-sort --unique | while IFS=',' read name value override; do local enum=$( RecordTypeMnemonicToEnum "${name}" ) printf "\t%-28s= %d," "${enum}" "${value}" if [ "${override}" -ne 0 ]; then printf " // OVERRIDE" fi printf "\n" done printf "\t\n" printf "}\tDNSRecordType;\n" } #============================================================================================================================ PrintValueToStringElseIf() { local -r first=${1} local -r last=${2} [ "${first}" -le "${last}" ] || ErrQuit "${first} > ${last}" shift 2 local stringArray=( "$@" ) if [ "${last}" -ne "${first}" ]; then printf "\telse if( ( inValue >= ${first} ) && ( inValue <= ${last} ) )\n" local -r arrayVarName="sNames_${first}_${last}" else printf "\telse if( inValue == ${first} )\n" local -r arrayVarName="sNames_${first}" fi printf "\t{\n" printf "\t\tstatic const char * const\t\t${arrayVarName}[] =\n" printf "\t\t{\n" local value=${first} for string in "${stringArray[@]}"; do printf "\t\t\t%-15s // %3d\n" "\"${string}\"," "${value}" value=$(( value + 1 )) done local -r stringCount=$(( value - first )) local -r expectedCount=$(( last - first + 1 )) [ "${stringCount}" -eq "${expectedCount}" ] || ErrQuit "${stringCount} != ${expectedCount}" printf "\t\t};\n" printf "\t\tstring = ${arrayVarName}[ inValue - ${first} ];\n" printf "\t}\n" } #============================================================================================================================ PrintValueToStringFunction() { local -r inputFile=${1} printf "const char *\tDNSRecordTypeValueToString( const int inValue )\n" printf "{\n" printf "\tswitch( inValue )\n" printf "\t{\n" < "${inputFile}" sort --field-separator=, --key=2,2 --numeric-sort --unique | { local stringArray=() while IFS=',' read name value override; do local enum=$( RecordTypeMnemonicToEnum "${name}" ) if [ "${override}" -eq 0 ]; then printf "\t\t%-32s%s" "case ${enum}:" "return( \"${name}\" );" else printf "\t\t%-32s%-24s // OVERRIDE" "case ${enum}:" "return( \"${name}\" );" fi printf "\n" done } printf "\t}\n" printf "\treturn( NULL );\n" printf "}\n" } #============================================================================================================================ PrintStringToValueFunction() { local -r inputFile=${1} printf "#include \n" printf "\n" printf "typedef struct\n" printf "{\n" printf "\tconst char *\t\tname;\n" printf "\tuint16_t\t\t\tvalue;\n" printf "\t\n" printf "}\t_DNSRecordTypeItem;\n" printf "\n" printf "static int\t_DNSRecordTypeStringToValueCmp( const void *inKey, const void *inElement );\n" printf "\n" printf "uint16_t\tDNSRecordTypeStringToValue( const char * const inString )\n" printf "{\n" printf "\t// The name-value table is sorted by name in ascending lexicographical order to allow going from name to\n" printf "\t// value in logarithmic time via a binary search.\n" printf "\t\n" printf "\tstatic const _DNSRecordTypeItem\t\tsTable[] =\n" printf "\t{\n" < "${inputFile}" sort --field-separator=, --key=1,1 --ignore-case --unique | while IFS=',' read name value override; do local enum=$( RecordTypeMnemonicToEnum "${name}" ) printf "\t\t%-16s%-28s }," "{ \"${name}\"," "${enum}" if [ "${override}" -ne 0 ]; then printf " // OVERRIDE" fi printf "\n" done printf "\t};\n" printf "\tconst _DNSRecordTypeItem *\t\t\titem;\n" printf "\t\n" printf "\titem = (_DNSRecordTypeItem *) bsearch( inString, sTable, sizeof( sTable ) / sizeof( sTable[ 0 ] ),\n" printf "\t\tsizeof( sTable[ 0 ] ), _DNSRecordTypeStringToValueCmp );\n" printf "\treturn( item ? item->value : 0 );\n" printf "}\n" printf "\n" printf "static int\t_DNSRecordTypeStringToValueCmp( const void * const inKey, const void * const inElement )\n" printf "{\n" printf "\tconst _DNSRecordTypeItem * const\t\titem = (const _DNSRecordTypeItem *) inElement;\n" printf "\treturn( strcasecmp( (const char *) inKey, item->name ) );\n" printf "}\n" } #============================================================================================================================ ExitHandler() { if [ -d "${tempDir}" ]; then rm -fr "${tempDir}" fi } #============================================================================================================================ PrintAutoGenNote() { printf "// This code was autogenerated on $( date -u '+%Y-%m-%d' ) by $( basename ${script} ) version ${version}\n" printf "// Data source URL: ${recordTypesURL}\n" printf "// Overrides: " if [ "${#}" -gt 0 ]; then local separator="" for override in "${@}"; do printf "%s'%s'" "${separator}" "${override}" separator=", " done printf "\n" else printf "none\n" fi printf "\n" } #============================================================================================================================ main() { local -a overrides while getopts ":hO:V" option; do case "${option}" in h) PrintHelp exit 0 ;; O) overrides+=( "${OPTARG}" ) ;; V) echo "$( basename "${script}" ) version ${version}" exit 0 ;; :) ErrQuit "option '${OPTARG}' requires an argument." ;; *) ErrQuit "unknown option '${OPTARG}'." ;; esac done [ "${OPTIND}" -gt "$#" ] || ErrQuit "unexpected argument \"${!OPTIND}\"." trap ExitHandler EXIT tempDir=$( mktemp -d ) || ErrQuit "Failed to make temporary directory." declare -r originalRecordTypesFile="${tempDir}/recordTypesOriginal.csv" curl --output "${originalRecordTypesFile}" "${recordTypesURL}" || ErrQuit "Failed to download CSV file." declare -r overridesFile="${tempDir}/overrides.csv" for override in "${overrides[@]}"; do echo "${override}" done | GetNamesAndValues 1 > "${overridesFile}" declare -r recordTypesFile="${tempDir}/recordTypes.csv" < "${originalRecordTypesFile}" GetNamesAndValues 0 > "${recordTypesFile}" declare -r tempFile="${tempDir}/temp.csv" cat "${overridesFile}" "${recordTypesFile}" | sort --field-separator=, --key=2,2 --unique --numeric-sort > "${tempFile}" cat "${overridesFile}" "${tempFile}" | sort --field-separator=, --key=1,1 --unique --ignore-case > "${recordTypesFile}" PrintAutoGenNote "${overrides[@]}" PrintRecordTypesEnum "${recordTypesFile}" printf "\n" PrintAutoGenNote "${overrides[@]}" PrintValueToStringFunction "${recordTypesFile}" printf "\n" PrintAutoGenNote "${overrides[@]}" PrintStringToValueFunction "${recordTypesFile}" } main "$@"