cython pkcs11
''' | |
This module provides functions for easily generating PKCS11 bindings. | |
Intended call method is: | |
generate_pxd(headers_directory, vendor_top_level_include_header) | |
''' | |
import re | |
import collections | |
import parsimonious.grammar | |
from parsimonious.nodes import NodeVisitor | |
def generate_pxd(include_path, header_name): | |
''' | |
include path should be the directory containing pkcs11f.h and pkcs11t.h | |
header name is the name of the header the functions will actually load from | |
''' | |
pkcs11f_data = open(include_path + '/pkcs11f.h').read() | |
pkcs11t_data = open(include_path + '/pkcs11t.h').read() | |
lines = pxd_lines( | |
parse_pkcs11f(pkcs11f_data), | |
parse_pkcs11t(pkcs11t_data)) | |
output = '# auto generated by p112pxd.py DO NOT EDIT\n' | |
output += 'cdef extern from "{0}" nogil:\n'.format(header_name) | |
output += "\n".join([e.replace('CK_PTR', '*') for e in lines]) + "\n" | |
open('pkcs11.pxd', 'wb').write(output) | |
def pxd_lines(parsed_funcs, parsed_types): | |
''' | |
converts the output from parse_pkcs11f and parse_pkcs11t | |
into a form suitable for inclusion in a pxd | |
''' | |
lines = [] | |
for name, typ in parsed_types.typedefs.items(): | |
lines.append('ctypedef' + typ + name) | |
for name, members in parsed_types.func_typedefs.items(): | |
lines.append('ctypedef CK_RV (*{0})({1})'.format( | |
name, ", ".join([typ + " " + name for typ, name in members]))) | |
for name, members in parsed_types.typedef_structs.items(): | |
lines.append('ctypedef struct {0}:'.format(name.strip())) | |
if not members: | |
lines.append(' pass') | |
for typ, name in members: | |
lines.append(' ' + typ.strip() + ' ' + name.strip()) | |
lines.append('') | |
for alias, struct in parsed_types.struct_aliases.items(): | |
#treat struct typedefs as forward-declared opaque structs | |
lines.append('cdef struct' + alias + ':') | |
lines.append(' pass') | |
for name, args in parsed_funcs.items(): | |
lines.append('CK_RV {0}({1})'.format( | |
name, ', '.join(["{0} {1}".format(*arg) for arg in args]))) | |
return_value_dict = collections.OrderedDict() | |
for name, value in parsed_types.defines.items(): | |
try: | |
int(value, 0) | |
except: | |
lines.append('#' + name + value) | |
else: | |
lines.append('unsigned long' + name + ' #' + value) | |
if name.strip().startswith('CKR_'): | |
return_value_dict[value] = name.strip()[4:] | |
lines = [" " + e for e in lines] # apply indentation | |
lines.append('') | |
lines.append('CKR_NAMES = {') | |
for val, name in return_value_dict.items(): | |
lines.append(' ' + val + ': "' + name + '",') | |
lines.append('}') | |
return lines | |
def parse_pkcs11f(data): | |
'parse pkcs11f.h to a dict of {funcname: [(arg_type, arg_name), ...]}' | |
# normalize all white-space to single spaces between tokens | |
tokenized = re.sub("\s+", " ", data, flags=re.M) | |
# run PEG grammar over tokenized output to generate AST | |
ast = TOKENIZED_PKCS11F_PARSER.parse(tokenized) | |
# use NodeVisitor subclass to extract func name and arg list from ast | |
return dict(_FuncVisitor().visit(ast)) | |
def parse_pkcs11t(data): | |
''' | |
parse pkcs11t.h, return an object with dicts of file contents as attributes | |
defines -- {name: value} | |
typedef_structs -- {name: [(type, name), ...]} | |
typedefs -- {defined type: definition} (e.g. {"CK_ULONG": "unsigned long"}) | |
struct_aliases -- {defined type: definition} (e.g. {"CK_FOO_PTR": "CK_FOO PTR"}) | |
''' | |
# collapse line continuations and eliminate C++ style comments | |
data = data.replace('\\\n', '') | |
data = re.sub("//.*\n", "", data) | |
# normalize all white-space to single spaces between tokens | |
tokenized = re.sub("\s+", " ", data, flags=re.M) | |
# run PEG grammar over tokenized output to generate AST | |
ast = TOKENIZED_PKCS11T_PARSER.parse(tokenized) | |
# extract the useful bits | |
visitor = _TypeVisitor() | |
visitor.visit(ast) | |
return visitor | |
TOKENIZED_PKCS11F_GRAMMAR = r''' | |
file = ( comment / func / " " )* | |
func = func_hdr func_args | |
func_hdr = "CK_PKCS11_FUNCTION_INFO(" name ")" | |
func_args = arg_hdr " (" arg* " ); #endif" | |
arg_hdr = " #ifdef CK_NEED_ARG_LIST" (" " comment)? | |
arg = " " type " " name ","? " " comment | |
name = identifier | |
type = identifier | |
identifier = ~"[A-Z_][A-Z0-9_]*"i | |
comment = ~"(/\*.*?\*/)"ms | |
''' | |
TOKENIZED_PKCS11F_PARSER = parsimonious.grammar.Grammar( | |
TOKENIZED_PKCS11F_GRAMMAR) | |
TOKENIZED_PKCS11T_GRAMMAR = r''' | |
file = ( comment / define / typedef / struct_typedef / func_typedef / struct_alias_typedef / ignore )* | |
typedef = " typedef" type identifier ";" | |
struct_typedef = " typedef struct" identifier " "? "{" (comment / member)* " }" identifier ";" | |
struct_alias_typedef = " typedef struct" identifier " CK_PTR"? identifier ";" | |
func_typedef = " typedef CK_CALLBACK_FUNCTION(CK_RV," identifier ")(" (identifier identifier ","? comment?)* " );" member = identifier identifier array_size? ";" comment? | |
array_size = "[" ~"[0-9]"+ "]" | |
define = "#define" identifier (hexval / decval / " (~0UL)" / identifier / ~" \([A-Z_]*\|0x[0-9]{8}\)" ) | |
hexval = ~" 0x[A-F0-9]{8}"i | |
decval = ~" [0-9]+" | |
type = " unsigned char" / " unsigned long int" / " long int" / (identifier " CK_PTR") / identifier | |
identifier = " "? ~"[A-Z_][A-Z0-9_]*"i | |
comment = " "? ~"(/\*.*?\*/)"ms | |
ignore = ( " #ifndef" identifier ) / " #endif" / " " | |
''' | |
TOKENIZED_PKCS11T_PARSER = parsimonious.grammar.Grammar( | |
TOKENIZED_PKCS11T_GRAMMAR) | |
class _FuncVisitor(NodeVisitor): | |
def visit_func(self, node, visited_children): | |
func = node | |
# note this depends on the structure of func parsing grammar | |
func_hdr, func_args = func.children | |
fname = func_hdr.children[1].text | |
arg_nodes = func_args.children[2].children # step into arg* | |
args = [] | |
for node in arg_nodes: | |
typ, name = [e for e in node.children if e.expr_name == 'identifier'] | |
args.append((typ.text, name.text)) | |
return (fname, args) | |
def generic_visit(self, node, visited_children): | |
while len(visited_children) == 1: | |
visited_children = visited_children[0] | |
return [e for e in visited_children if type(e) != list or e != []] | |
class _TypeVisitor(NodeVisitor): | |
def __init__(self): | |
self.defines = collections.OrderedDict() | |
self.typedef_structs = collections.OrderedDict() | |
self.typedefs = collections.OrderedDict() | |
self.struct_aliases = collections.OrderedDict() | |
self.func_typedefs = collections.OrderedDict() | |
def visit_define(self, node, visited_children): | |
name = node.children[1].text | |
value = node.children[2].text | |
self.defines[name] = value | |
def visit_struct_typedef(self, node, visited_children): | |
member_nodes = node.children[4] | |
member_nodes = [m.children[0] for m in member_nodes] | |
members = [] | |
for mn in member_nodes: | |
if mn.expr_name != 'member': | |
continue # skip comments | |
members.append((mn.children[0].text, mn.children[1].text + mn.children[2].text)) | |
self.typedef_structs[node.children[1].text] = members | |
def visit_struct_alias_typedef(self, node, visited_children): | |
self.struct_aliases[node.children[3].text] = node.children[1].text + node.children[2].text | |
def visit_typedef(self, node, visited_children): | |
self.typedefs[node.children[2].text] = node.children[1].text | |
def visit_func_typedef(self, node, visited_children): | |
name = node.children[1].text | |
member_nodes = node.children[3] | |
members = [] | |
for mn in member_nodes: | |
members.append((mn.children[0].text, mn.children[1].text)) | |
self.func_typedefs[name] = members | |
def generic_visit(self, node, visited_children): | |
return |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment