Files
RustPython/scripts/make_ssl_data_rs.py
2025-12-12 22:46:39 +09:00

168 lines
5.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Generate Rust SSL error mapping code from OpenSSL sources.
This is based on CPython's Tools/ssl/make_ssl_data.py but generates
Rust code instead of C headers.
It takes two arguments:
- the path to the OpenSSL source tree (e.g. git checkout)
- the path to the Rust file to be generated (e.g. stdlib/src/ssl/ssl_data.rs)
- error codes are version specific
"""
import argparse
import datetime
import operator
import os
import re
import sys
parser = argparse.ArgumentParser(
description="Generate ssl_data.rs from OpenSSL sources"
)
parser.add_argument("srcdir", help="OpenSSL source directory")
parser.add_argument("output", nargs="?", default=None)
def _file_search(fname, pat):
with open(fname, encoding="utf-8") as f:
for line in f:
match = pat.search(line)
if match is not None:
yield match
def parse_err_h(args):
"""Parse err codes, e.g. ERR_LIB_X509: 11"""
pat = re.compile(r"#\s*define\W+ERR_LIB_(\w+)\s+(\d+)")
lib2errnum = {}
for match in _file_search(args.err_h, pat):
libname, num = match.groups()
lib2errnum[libname] = int(num)
return lib2errnum
def parse_openssl_error_text(args):
"""Parse error reasons, X509_R_AKID_MISMATCH"""
# ignore backslash line continuation for now
pat = re.compile(r"^((\w+?)_R_(\w+)):(\d+):")
for match in _file_search(args.errtxt, pat):
reason, libname, errname, num = match.groups()
if "_F_" in reason:
# ignore function codes
continue
num = int(num)
yield reason, libname, errname, num
def parse_extra_reasons(args):
"""Parse extra reasons from openssl.ec"""
pat = re.compile(r"^R\s+((\w+)_R_(\w+))\s+(\d+)")
for match in _file_search(args.errcodes, pat):
reason, libname, errname, num = match.groups()
num = int(num)
yield reason, libname, errname, num
def gen_library_codes_rust(args):
"""Generate Rust phf map for library codes"""
yield "// Maps lib_code -> library name"
yield '// Example: 20 -> "SSL"'
yield "pub static LIBRARY_CODES: phf::Map<u32, &'static str> = phf_map! {"
# Deduplicate: keep the last one if there are duplicates
seen = {}
for libname in sorted(args.lib2errnum):
lib_num = args.lib2errnum[libname]
seen[lib_num] = libname
for lib_num in sorted(seen.keys()):
libname = seen[lib_num]
yield f' {lib_num}u32 => "{libname}",'
yield "};"
yield ""
def gen_error_codes_rust(args):
"""Generate Rust phf map for error codes"""
yield "// Maps encoded (lib, reason) -> error mnemonic"
yield '// Example: encode_error_key(20, 134) -> "CERTIFICATE_VERIFY_FAILED"'
yield "// Key encoding: (lib << 32) | reason"
yield "pub static ERROR_CODES: phf::Map<u64, &'static str> = phf_map! {"
for reason, libname, errname, num in args.reasons:
if libname not in args.lib2errnum:
continue
lib_num = args.lib2errnum[libname]
# Encode (lib, reason) as single u64
key = (lib_num << 32) | num
yield f' {key}u64 => "{errname}",'
yield "};"
yield ""
def main():
args = parser.parse_args()
args.err_h = os.path.join(args.srcdir, "include", "openssl", "err.h")
if not os.path.isfile(args.err_h):
# Fall back to infile for OpenSSL 3.0.0
args.err_h += ".in"
args.errcodes = os.path.join(args.srcdir, "crypto", "err", "openssl.ec")
args.errtxt = os.path.join(args.srcdir, "crypto", "err", "openssl.txt")
if not os.path.isfile(args.errtxt):
parser.error(f"File {args.errtxt} not found in srcdir\n.")
# {X509: 11, ...}
args.lib2errnum = parse_err_h(args)
# [('X509_R_AKID_MISMATCH', 'X509', 'AKID_MISMATCH', 110), ...]
reasons = []
reasons.extend(parse_openssl_error_text(args))
reasons.extend(parse_extra_reasons(args))
# sort by libname, numeric error code
args.reasons = sorted(reasons, key=operator.itemgetter(0, 3))
lines = [
"// File generated by tools/make_ssl_data_rs.py",
f"// Generated on {datetime.datetime.now(datetime.timezone.utc).isoformat()}",
f"// Source: OpenSSL from {args.srcdir}",
"// spell-checker: disable",
"",
"use phf::phf_map;",
"",
]
lines.extend(gen_library_codes_rust(args))
lines.extend(gen_error_codes_rust(args))
# Add helper function
lines.extend(
[
"/// Helper function to create encoded key from (lib, reason) pair",
"#[inline]",
"pub fn encode_error_key(lib: i32, reason: i32) -> u64 {",
" ((lib as u64) << 32) | (reason as u64 & 0xFFFFFFFF)",
"}",
"",
]
)
if args.output is None:
for line in lines:
print(line)
else:
with open(args.output, "w") as output:
for line in lines:
print(line, file=output)
print(f"Generated {args.output}")
print(f"Found {len(args.lib2errnum)} library codes")
print(f"Found {len(args.reasons)} error codes")
if __name__ == "__main__":
main()