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 LMS 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 PKCS7 ); 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 $define_name = "PKCS7" if ($define_name eq "PKCS7"); 140 141 my $include_name = $module_name; 142 $include_name =~ tr/A-Z/a-z/; 143 144 # Fix faulty ones 145 $include_name = "net_sockets" if ($module_name eq "NET"); 146 147 $included_headers{"${include_name}.h"} = $module_name; 148 149 my $found_ll = grep $_ eq $module_name, @low_level_modules; 150 my $found_hl = grep $_ eq $module_name, @high_level_modules; 151 if (!$found_ll && !$found_hl) 152 { 153 printf("Error: Do not know how to handle: $module_name\n"); 154 exit 1; 155 } 156 157 my $code_check; 158 my $old_define; 159 my $white_space; 160 my $first; 161 162 if ($found_ll) 163 { 164 $code_check = \$ll_code_check; 165 $old_define = \$ll_old_define; 166 $white_space = ' '; 167 } 168 else 169 { 170 $code_check = \$hl_code_check; 171 $old_define = \$hl_old_define; 172 $white_space = ' '; 173 } 174 175 if ($define_name ne ${$old_define}) 176 { 177 if (${$old_define} ne "") 178 { 179 ${$code_check} .= "#endif /* "; 180 $first = 0; 181 foreach my $dep (split(/,/, ${$old_define})) 182 { 183 ${$code_check} .= " || " if ($first++); 184 ${$code_check} .= "MBEDTLS_${dep}_C"; 185 } 186 ${$code_check} .= " */\n\n"; 187 } 188 189 ${$code_check} .= "#if "; 190 $headers .= "#if " if ($include_name ne ""); 191 $first = 0; 192 foreach my $dep (split(/,/, ${define_name})) 193 { 194 ${$code_check} .= " || " if ($first); 195 $headers .= " || " if ($first++); 196 197 ${$code_check} .= "defined(MBEDTLS_${dep}_C)"; 198 $headers .= "defined(MBEDTLS_${dep}_C)" if 199 ($include_name ne ""); 200 } 201 ${$code_check} .= "\n"; 202 $headers .= "\n#include \"mbedtls/${include_name}.h\"\n". 203 "#endif\n\n" if ($include_name ne ""); 204 ${$old_define} = $define_name; 205 } 206 207 ${$code_check} .= "${white_space}case -($error_name):\n". 208 "${white_space} return( \"$module_name - $description\" );\n" 209}; 210 211if ($ll_old_define ne "") 212{ 213 $ll_code_check .= "#endif /* "; 214 my $first = 0; 215 foreach my $dep (split(/,/, $ll_old_define)) 216 { 217 $ll_code_check .= " || " if ($first++); 218 $ll_code_check .= "MBEDTLS_${dep}_C"; 219 } 220 $ll_code_check .= " */\n"; 221} 222if ($hl_old_define ne "") 223{ 224 $hl_code_check .= "#endif /* "; 225 my $first = 0; 226 foreach my $dep (split(/,/, $hl_old_define)) 227 { 228 $hl_code_check .= " || " if ($first++); 229 $hl_code_check .= "MBEDTLS_${dep}_C"; 230 } 231 $hl_code_check .= " */\n"; 232} 233 234$error_format =~ s/HEADER_INCLUDED\n/$headers/g; 235$error_format =~ s/LOW_LEVEL_CODE_CHECKS\n/$ll_code_check/g; 236$error_format =~ s/HIGH_LEVEL_CODE_CHECKS\n/$hl_code_check/g; 237 238open(ERROR_FILE, ">$error_file") or die "Opening destination file '$error_file': $!"; 239print ERROR_FILE $error_format; 240close(ERROR_FILE); 241 242my $errors = 0; 243for my $include_name (@necessary_include_files) 244{ 245 if (not $included_headers{$include_name}) 246 { 247 print STDERR "The header file \"$include_name\" defines error codes but has not been included!\n"; 248 ++$errors; 249 } 250} 251 252exit !!$errors; 253