Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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