1#!/usr/bin/env python3 2"""Generate library/psa_crypto_driver_wrappers.c 3 4 This module is invoked by the build scripts to auto generate the 5 psa_crypto_driver_wrappers.c based on template files in 6 script/data_files/driver_templates/. 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 23import sys 24import os 25import json 26from typing import NewType, Dict, Any 27from traceback import format_tb 28import argparse 29import jsonschema 30import jinja2 31from mbedtls_dev import build_tree 32 33JSONSchema = NewType('JSONSchema', object) 34# The Driver is an Object, but practically it's indexable and can called a dictionary to 35# keep MyPy happy till MyPy comes with a more composite type for JsonObjects. 36Driver = NewType('Driver', dict) 37 38 39class JsonValidationException(Exception): 40 def __init__(self, message="Json Validation Failed"): 41 self.message = message 42 super().__init__(self.message) 43 44 45class DriverReaderException(Exception): 46 def __init__(self, message="Driver Reader Failed"): 47 self.message = message 48 super().__init__(self.message) 49 50 51def render(template_path: str, driver_jsoncontext: list) -> str: 52 """ 53 Render template from the input file and driver JSON. 54 """ 55 environment = jinja2.Environment( 56 loader=jinja2.FileSystemLoader(os.path.dirname(template_path)), 57 keep_trailing_newline=True) 58 template = environment.get_template(os.path.basename(template_path)) 59 60 return template.render(drivers=driver_jsoncontext) 61 62 63def generate_driver_wrapper_file(template_dir: str, 64 output_dir: str, 65 driver_jsoncontext: list) -> None: 66 """ 67 Generate the file psa_crypto_driver_wrapper.c. 68 """ 69 driver_wrapper_template_filename = \ 70 os.path.join(template_dir, "psa_crypto_driver_wrappers.c.jinja") 71 72 result = render(driver_wrapper_template_filename, driver_jsoncontext) 73 74 with open(file=os.path.join(output_dir, "psa_crypto_driver_wrappers.c"), 75 mode='w', 76 encoding='UTF-8') as out_file: 77 out_file.write(result) 78 79 80def validate_json(driverjson_data: Driver, driverschema_list: dict) -> None: 81 """ 82 Validate the Driver JSON against an appropriate schema 83 the schema passed could be that matching an opaque/ transparent driver. 84 """ 85 driver_type = driverjson_data["type"] 86 driver_prefix = driverjson_data["prefix"] 87 try: 88 _schema = driverschema_list[driver_type] 89 jsonschema.validate(instance=driverjson_data, schema=_schema) 90 except KeyError as err: 91 # This could happen if the driverjson_data.type does not exist in the provided schema list 92 # schemas = {'transparent': transparent_driver_schema, 'opaque': opaque_driver_schema} 93 # Print onto stdout and stderr. 94 print("Unknown Driver type " + driver_type + 95 " for driver " + driver_prefix, str(err)) 96 print("Unknown Driver type " + driver_type + 97 " for driver " + driver_prefix, str(err), file=sys.stderr) 98 raise JsonValidationException() from err 99 100 except jsonschema.exceptions.ValidationError as err: 101 # Print onto stdout and stderr. 102 print("Error: Failed to validate data file: {} using schema: {}." 103 "\n Exception Message: \"{}\"" 104 " ".format(driverjson_data, _schema, str(err))) 105 print("Error: Failed to validate data file: {} using schema: {}." 106 "\n Exception Message: \"{}\"" 107 " ".format(driverjson_data, _schema, str(err)), file=sys.stderr) 108 raise JsonValidationException() from err 109 110 111def load_driver(schemas: Dict[str, Any], driver_file: str) -> Any: 112 """loads validated json driver""" 113 with open(file=driver_file, mode='r', encoding='UTF-8') as f: 114 json_data = json.load(f) 115 try: 116 validate_json(json_data, schemas) 117 except JsonValidationException as e: 118 raise DriverReaderException from e 119 return json_data 120 121 122def load_schemas(mbedtls_root: str) -> Dict[str, Any]: 123 """ 124 Load schemas map 125 """ 126 schema_file_paths = { 127 'transparent': os.path.join(mbedtls_root, 128 'scripts', 129 'data_files', 130 'driver_jsons', 131 'driver_transparent_schema.json'), 132 'opaque': os.path.join(mbedtls_root, 133 'scripts', 134 'data_files', 135 'driver_jsons', 136 'driver_opaque_schema.json') 137 } 138 driver_schema = {} 139 for key, file_path in schema_file_paths.items(): 140 with open(file=file_path, mode='r', encoding='UTF-8') as file: 141 driver_schema[key] = json.load(file) 142 return driver_schema 143 144 145def read_driver_descriptions(mbedtls_root: str, 146 json_directory: str, 147 jsondriver_list: str) -> list: 148 """ 149 Merge driver JSON files into a single ordered JSON after validation. 150 """ 151 driver_schema = load_schemas(mbedtls_root) 152 153 with open(file=os.path.join(json_directory, jsondriver_list), 154 mode='r', 155 encoding='UTF-8') as driver_list_file: 156 driver_list = json.load(driver_list_file) 157 158 return [load_driver(schemas=driver_schema, 159 driver_file=os.path.join(json_directory, driver_file_name)) 160 for driver_file_name in driver_list] 161 162 163def trace_exception(e: Exception, file=sys.stderr) -> None: 164 """Prints exception trace to the given TextIO handle""" 165 print("Exception: type: %s, message: %s, trace: %s" % ( 166 e.__class__, str(e), format_tb(e.__traceback__) 167 ), file) 168 169 170def main() -> int: 171 """ 172 Main with command line arguments. 173 """ 174 def_arg_mbedtls_root = build_tree.guess_mbedtls_root() 175 176 parser = argparse.ArgumentParser() 177 parser.add_argument('--mbedtls-root', default=def_arg_mbedtls_root, 178 help='root directory of mbedtls source code') 179 parser.add_argument('--template-dir', 180 help='directory holding the driver templates') 181 parser.add_argument('--json-dir', 182 help='directory holding the driver JSONs') 183 parser.add_argument('output_directory', nargs='?', 184 help='output file\'s location') 185 args = parser.parse_args() 186 187 mbedtls_root = os.path.abspath(args.mbedtls_root) 188 189 output_directory = args.output_directory if args.output_directory is not None else \ 190 os.path.join(mbedtls_root, 'library') 191 template_directory = args.template_dir if args.template_dir is not None else \ 192 os.path.join(mbedtls_root, 193 'scripts', 194 'data_files', 195 'driver_templates') 196 json_directory = args.json_dir if args.json_dir is not None else \ 197 os.path.join(mbedtls_root, 198 'scripts', 199 'data_files', 200 'driver_jsons') 201 202 try: 203 # Read and validate list of driver jsons from driverlist.json 204 merged_driver_json = read_driver_descriptions(mbedtls_root, 205 json_directory, 206 'driverlist.json') 207 except DriverReaderException as e: 208 trace_exception(e) 209 return 1 210 generate_driver_wrapper_file(template_directory, output_directory, merged_driver_json) 211 return 0 212 213 214if __name__ == '__main__': 215 sys.exit(main()) 216