Create a gist now

Instantly share code, notes, and snippets.

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