1#!/usr/bin/env perl 2 3# Generate error.c 4# 5# Usage: ./generate_errors.pl or scripts/generate_errors.pl without arguments, 6# or generate_errors.pl include_dir data_dir error_file 7# 8# Copyright The Mbed TLS Contributors 9# SPDX-License-Identifier: Apache-2.0 10# 11# Licensed under the Apache License, Version 2.0 (the "License"); you may 12# not use this file except in compliance with the License. 13# You may obtain a copy of the License at 14# 15# http://www.apache.org/licenses/LICENSE-2.0 16# 17# Unless required by applicable law or agreed to in writing, software 18# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 19# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20# See the License for the specific language governing permissions and 21# limitations under the License. 22 23use strict; 24use warnings; 25 26my ($include_dir, $data_dir, $error_file); 27 28if( @ARGV ) { 29 die "Invalid number of arguments" if scalar @ARGV != 3; 30 ($include_dir, $data_dir, $error_file) = @ARGV; 31 32 -d $include_dir or die "No such directory: $include_dir\n"; 33 -d $data_dir or die "No such directory: $data_dir\n"; 34} else { 35 $include_dir = 'include/mbedtls'; 36 $data_dir = 'scripts/data_files'; 37 $error_file = 'library/error.c'; 38 39 unless( -d $include_dir && -d $data_dir ) { 40 chdir '..' or die; 41 -d $include_dir && -d $data_dir 42 or die "Without arguments, must be run from root or scripts\n" 43 } 44} 45 46my $error_format_file = $data_dir.'/error.fmt'; 47 48my @low_level_modules = qw( AES ARIA ASN1 BASE64 BIGNUM 49 CAMELLIA CCM CHACHA20 CHACHAPOLY CMAC CTR_DRBG DES 50 ENTROPY ERROR GCM HKDF HMAC_DRBG MD5 51 NET OID PADLOCK PBKDF2 PLATFORM POLY1305 RIPEMD160 52 SHA1 SHA256 SHA512 THREADING ); 53my @high_level_modules = qw( CIPHER DHM ECP MD 54 PEM PK PKCS12 PKCS5 55 RSA SSL X509 ); 56 57undef $/; 58 59open(FORMAT_FILE, '<:crlf', "$error_format_file") or die "Opening error format file '$error_format_file': $!"; 60my $error_format = <FORMAT_FILE>; 61close(FORMAT_FILE); 62 63my @files = <$include_dir/*.h>; 64my @necessary_include_files; 65my @matches; 66foreach my $file (@files) { 67 open(FILE, '<:crlf', "$file"); 68 my $content = <FILE>; 69 close FILE; 70 my $found = 0; 71 while ($content =~ m[ 72 # Both the before-comment and the after-comment are optional. 73 # Only the comment content is a regex capture group. The comment 74 # start and end parts are outside the capture group. 75 (?:/\*[*!](?!<) # Doxygen before-comment start 76 ((?:[^*]|\*+[^*/])*) # $1: Comment content (no */ inside) 77 \*/)? # Comment end 78 \s*\#\s*define\s+(MBEDTLS_ERR_\w+) # $2: name 79 \s+\-(0[Xx][0-9A-Fa-f]+)\s* # $3: value (without the sign) 80 (?:/\*[*!]< # Doxygen after-comment start 81 ((?:[^*]|\*+[^*/])*) # $4: Comment content (no */ inside) 82 \*/)? # Comment end 83 ]gsx) { 84 my ($before, $name, $value, $after) = ($1, $2, $3, $4); 85 # Discard Doxygen comments that are coincidentally present before 86 # an error definition but not attached to it. This is ad hoc, based 87 # on what actually matters (or mattered at some point). 88 undef $before if defined($before) && $before =~ /\s*\\name\s/s; 89 die "Description neither before nor after $name in $file\n" 90 if !defined($before) && !defined($after); 91 die "Description both before and after $name in $file\n" 92 if defined($before) && defined($after); 93 my $description = (defined($before) ? $before : $after); 94 $description =~ s/^\s+//; 95 $description =~ s/\n( *\*)? */ /g; 96 $description =~ s/\.?\s+$//; 97 push @matches, [$name, $value, $description]; 98 ++$found; 99 } 100 if ($found) { 101 my $include_name = $file; 102 $include_name =~ s!.*/!!; 103 push @necessary_include_files, $include_name; 104 } 105} 106 107my $ll_old_define = ""; 108my $hl_old_define = ""; 109 110my $ll_code_check = ""; 111my $hl_code_check = ""; 112 113my $headers = ""; 114my %included_headers; 115 116my %error_codes_seen; 117 118foreach my $match (@matches) 119{ 120 my ($error_name, $error_code, $description) = @$match; 121 122 die "Duplicated error code: $error_code ($error_name)\n" 123 if( $error_codes_seen{$error_code}++ ); 124 125 $description =~ s/\\/\\\\/g; 126 127 my ($module_name) = $error_name =~ /^MBEDTLS_ERR_([^_]+)/; 128 129 # Fix faulty ones 130 $module_name = "BIGNUM" if ($module_name eq "MPI"); 131 $module_name = "CTR_DRBG" if ($module_name eq "CTR"); 132 $module_name = "HMAC_DRBG" if ($module_name eq "HMAC"); 133 134 my $define_name = $module_name; 135 $define_name = "X509_USE,X509_CREATE" if ($define_name eq "X509"); 136 $define_name = "ASN1_PARSE" if ($define_name eq "ASN1"); 137 $define_name = "SSL_TLS" if ($define_name eq "SSL"); 138 $define_name = "PEM_PARSE,PEM_WRITE" if ($define_name eq "PEM"); 139 140 my $include_name = $module_name; 141 $include_name =~ tr/A-Z/a-z/; 142 143 # Fix faulty ones 144 $include_name = "net_sockets" if ($module_name eq "NET"); 145 146 $included_headers{"${include_name}.h"} = $module_name; 147 148 my $found_ll = grep $_ eq $module_name, @low_level_modules; 149 my $found_hl = grep $_ eq $module_name, @high_level_modules; 150 if (!$found_ll && !$found_hl) 151 { 152 printf("Error: Do not know how to handle: $module_name\n"); 153 exit 1; 154 } 155 156 my $code_check; 157 my $old_define; 158 my $white_space; 159 my $first; 160 161 if ($found_ll) 162 { 163 $code_check = \$ll_code_check; 164 $old_define = \$ll_old_define; 165 $white_space = ' '; 166 } 167 else 168 { 169 $code_check = \$hl_code_check; 170 $old_define = \$hl_old_define; 171 $white_space = ' '; 172 } 173 174 if ($define_name ne ${$old_define}) 175 { 176 if (${$old_define} ne "") 177 { 178 ${$code_check} .= "#endif /* "; 179 $first = 0; 180 foreach my $dep (split(/,/, ${$old_define})) 181 { 182 ${$code_check} .= " || " if ($first++); 183 ${$code_check} .= "MBEDTLS_${dep}_C"; 184 } 185 ${$code_check} .= " */\n\n"; 186 } 187 188 ${$code_check} .= "#if "; 189 $headers .= "#if " if ($include_name ne ""); 190 $first = 0; 191 foreach my $dep (split(/,/, ${define_name})) 192 { 193 ${$code_check} .= " || " if ($first); 194 $headers .= " || " if ($first++); 195 196 ${$code_check} .= "defined(MBEDTLS_${dep}_C)"; 197 $headers .= "defined(MBEDTLS_${dep}_C)" if 198 ($include_name ne ""); 199 } 200 ${$code_check} .= "\n"; 201 $headers .= "\n#include \"mbedtls/${include_name}.h\"\n". 202 "#endif\n\n" if ($include_name ne ""); 203 ${$old_define} = $define_name; 204 } 205 206 ${$code_check} .= "${white_space}case -($error_name):\n". 207 "${white_space} return( \"$module_name - $description\" );\n" 208}; 209 210if ($ll_old_define ne "") 211{ 212 $ll_code_check .= "#endif /* "; 213 my $first = 0; 214 foreach my $dep (split(/,/, $ll_old_define)) 215 { 216 $ll_code_check .= " || " if ($first++); 217 $ll_code_check .= "MBEDTLS_${dep}_C"; 218 } 219 $ll_code_check .= " */\n"; 220} 221if ($hl_old_define ne "") 222{ 223 $hl_code_check .= "#endif /* "; 224 my $first = 0; 225 foreach my $dep (split(/,/, $hl_old_define)) 226 { 227 $hl_code_check .= " || " if ($first++); 228 $hl_code_check .= "MBEDTLS_${dep}_C"; 229 } 230 $hl_code_check .= " */\n"; 231} 232 233$error_format =~ s/HEADER_INCLUDED\n/$headers/g; 234$error_format =~ s/LOW_LEVEL_CODE_CHECKS\n/$ll_code_check/g; 235$error_format =~ s/HIGH_LEVEL_CODE_CHECKS\n/$hl_code_check/g; 236 237open(ERROR_FILE, ">$error_file") or die "Opening destination file '$error_file': $!"; 238print ERROR_FILE $error_format; 239close(ERROR_FILE); 240 241my $errors = 0; 242for my $include_name (@necessary_include_files) 243{ 244 if (not $included_headers{$include_name}) 245 { 246 print STDERR "The header file \"$include_name\" defines error codes but has not been included!\n"; 247 ++$errors; 248 } 249} 250 251exit !!$errors; 252