Skip to content

Instantly share code, notes, and snippets.

@joshkunz
Last active August 29, 2015 14:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joshkunz/cc2c9548d4a2d5925756 to your computer and use it in GitHub Desktop.
Save joshkunz/cc2c9548d4a2d5925756 to your computer and use it in GitHub Desktop.

The file decide.py contains the code necessary to call your C DECIDE function from python code. I wrote this to try and facilitate easier fuzzing of my team's DECIDE implementation. If you have any questions about the setup or usage feel free to ask on the forums. Also, I've tried to add at least a little documentation to decide.py so you can always try checking in there as well.

Setup

Before I explain the interface in detail, I need to explain how to generate a shared library containing your DECIDE code. Luckily, it's pretty easy. When you compile any object files, make sure to add the flag: -fpic and when you generate the library use the flag -shared. I've tested and this works on OSX and Linux when using gcc or clang.

For example, if I wanted to build a shared library called _decide.so (.so is traditional for shared libraries) from a single decide.c source file, I would run the commands:

$ gcc -o decide.o -fpic decide.c
$ gcc -o _decide.so -shared decide.o

Pretty easy. One word of caution though, I'm naming my shared library _decide.so because calling decide.so can conflict with decide.py. If both decide.so and decide.py are in the same directory, in python, import decide will cause an error because shared libraries have precedence over python files for imports.

Interface

class Decide(lib_path)

An instance of this class represents the DECIDE code. The instance itself acts like a function that when passed an Input instance will return an Output instance that reflects the state of the global variables after the DECIDE function has been run.

The argument lib_path is the path to the shared library containing the DECIDE function and variables. Some platforms can be a little finicky about this path, so try using an absolute path, or add a leading "./" when using a relative path.

class LCM()

LCM is a helper class for working with the LCM matrix. It has a single attribute matrix that is a 15x15 matrix with the modifications made using the class's methods. Those methods are LCM.notused(i, j), LCM.andd(i, j), and LCM.orr(i, j) that set the value of lcm[i][j] to the value you would expect.

class Input(points, params, lcm, active)

An instance of this class defines the inputs to decide. An example can be seen below.

attribute description
points A list of points. Each point is an (x, y) tuple.
params A dictionary mapping fields of the PARAMETERS structure to values. Any values that are un-specified will be set to their defaults (usually 0). The field names are case-insensitive.
lcm A 15x15 matrix containing the values NOTUSED, ANDD, and ORR. The constants NOTUSED, ANDD, and ORR are defined in decide.py and can be imported. The class LCM provides a higher-level interface for this matrix if needed.
active A 15 item list of booleans that describes which LICs are active. This list becomes the diagonal elements of the PUM.

class Output(points, params, lcm, active, pum, cmv, fuv, launch)

This class defines the output of the DECIDE function. An instance of it is returned when DECIDEis called. points, params, lcm and active are the same as in the Input class except they are loaded after DECIDE has run. They could be used to check and make sure that DECIDE doesn't change any values over its run. The remaining fields are documented below:

attribute description
pum A 15x15 boolean matrix containing the values found in the PUM after DECIDE has ran.
cmv A 15 item list of boolean values from the CMV
fuv A 15 item list of boolean values from the FUV
launch A boolean value matching the value of LAUNCH

Example

Here's a short example illustrating how you might test lic_1 code.

# Import interface
from decide import Input, Decide, LCM, LIC_COUNT

# Load DECIDE
DECIDE = Decide("./_decide.so")

# Set-upt the test-case
points = [(0, 0), (7.00, 0.00), (4.949747, 4.949747)]
radius = 4.04
expected = False
active = [False] * LIC_COUNT
active[1] = True

# Run the Test 
output = DECIDE(Input(points, {"radius1": radius}, LCM().matrix, active))

# Check output
assert output.cmv[1] == expected
import ctypes
from ctypes import (c_int, c_double,
pointer, POINTER)
from collections import namedtuple
LIC_COUNT = 15
NOTUSED, ORR, ANDD = range(777, 777+3)
def truth(v):
"Returns 'True' when 'v' is truthy. False otherwise."
return True if v else False
def matrix_2d(lc, lr, default=0):
"Returns a matrix with 'lc' columns and 'lr' rows initialized to 'default'."
matrix = []
for i in xrange(lc):
matrix.append([NOTUSED] * lr)
return matrix
class LCM(object):
def __init__(self, matrix=None):
self.matrix = matrix
if self.matrix is None:
self.matrix = matrix_2d(LIC_COUNT, LIC_COUNT, default=NOTUSED)
def notused(self, a, b):
self.matrix[a][b] = NOTUSED
def andd(self, a, b):
self.matrix[a][b] = ANDD
def orr(self, a, b):
self.matrix[a][b] = ORR
class Input(namedtuple("Input", ["points", "params", "lcm", "active"])):
def _fill_points(self, d):
d.NUMPOINTS.value = len(self.points)
for i in xrange(len(self.points)):
d.X[i] = self.points[i][0]
d.Y[i] = self.points[i][1]
def _fill_params(self, d):
for k, v in self.params.iteritems():
setattr(d.PARAMETERS, k.upper(), v)
def _fill_lcm(self, d):
for i in xrange(LIC_COUNT):
for j in xrange(LIC_COUNT):
d.LCM[i][j] = self.lcm[i][j]
def _fill_pum(self, d):
for i in xrange(LIC_COUNT):
d.PUM[i][i] = self.active[i]
def _fill(self, d):
self._fill_points(d)
self._fill_params(d)
self._fill_lcm(d)
self._fill_pum(d)
class _PARAMETERS(ctypes.Structure):
_fields_ = [ ("LENGTH1", c_double),
("RADIUS1", c_double),
("EPSILON", c_double),
("AREA1", c_double),
("Q_PTS", c_int),
("QUADS", c_int),
("DIST", c_double),
("N_PTS", c_int),
("K_PTS", c_int),
("A_PTS", c_int),
("B_PTS", c_int),
("C_PTS", c_int),
("D_PTS", c_int),
("E_PTS", c_int),
("F_PTS", c_int),
("G_PTS", c_int),
("LENGTH2", c_double),
("RADIUS2", c_double),
("AREA2", c_double) ]
class Output(namedtuple("Output", ["points", "params", "lcm", "active",
"pum", "cmv", "fuv", "launch"])):
@classmethod
def from_run(kls, d):
points = []
for i in xrange(d.NUMPOINTS.value):
points.append((d.X[i], d.Y[i]))
params = {}
for (k, _) in _PARAMETERS._fields_:
params[k.lower()] = getattr(d.PARAMETERS, k)
lcm = matrix_2d(LIC_COUNT, LIC_COUNT, default=NOTUSED)
for i in xrange(LIC_COUNT):
for j in xrange(LIC_COUNT):
lcm[i][j] = d.LCM[i][j]
pum = matrix_2d(LIC_COUNT, LIC_COUNT, default=False)
for i in xrange(LIC_COUNT):
for j in xrange(LIC_COUNT):
pum[i][j] = truth(d.PUM[i][j])
active = [pum[i][i] for i in xrange(LIC_COUNT)]
cmv = [truth(d.CMV[i]) for i in xrange(LIC_COUNT)]
fuv = [truth(d.FUV[i]) for i in xrange(LIC_COUNT)]
launch = truth(d.LAUNCH.value)
return kls(points, params, lcm, active, pum, cmv, fuv, launch)
_COORDINATE = POINTER(c_double)
# Where c_int in the case below is actually a CONNECTORS enum
_VECTOR = POINTER(c_int)
_CMATRIX = POINTER(_VECTOR)
_BMATRIX = _CMATRIX
class Decide(object):
"Class representing the decide code."
def _matrix(self, c, r, type):
"Generage a spine-style matrix with c columns and r rows."
spine = (POINTER(type) * c)()
rows = []
for i in xrange(c):
row = (type * r)()
spine[i] = row
rows.append(row)
return [spine, rows]
def blank(self, numpoints):
"""Sets all of the global variables to their empty states. 'numpoints'
is the number of points to allocate and the value of NUMPOINTS."""
self._X = self.X.contents = (c_double * numpoints)()
self._Y = self.Y.contents = (c_double * numpoints)()
self.NUMPOINTS.value = numpoints
self._LCM = self._matrix(LIC_COUNT, LIC_COUNT, c_int)
self._PUM = self._matrix(LIC_COUNT, LIC_COUNT, c_int)
self.LCM.contents = self._LCM[0]
self.PUM.contents = self._PUM[0]
self._CMV = self.CMV.contents = (c_int * LIC_COUNT)()
self._FUV = self.FUV.contents = (c_int * LIC_COUNT)()
self.LAUNCH.value = False
def __init__(self, dll):
"""Initializes a new Decide instance, 'dll' is a path to the dynamic
library with 'DECIDE' in it."""
self.lib = ctypes.CDLL(dll)
self.lib.DECIDE.restype = None
self.blank(0)
def close(self):
return self.lib.close()
def __call__(self, input):
"""Runs DECIDE with the given input values. 'input' is an instance of
the Input class defined above. This function returns an instance of
the Output class with the state after DECIDE has been run."""
self.blank(len(input.points))
input._fill(self)
self.lib.DECIDE()
o = Output.from_run(self)
self.blank(0)
return o
# Low-level interface for the DECIDE code.
@property
def X(self):
return _COORDINATE.in_dll(self.lib, "Y")
@property
def Y(self):
return _COORDINATE.in_dll(self.lib, "X")
@property
def PARAMETERS(self):
return _PARAMETERS.in_dll(self.lib, "PARAMETERS")
@property
def NUMPOINTS(self):
return c_int.in_dll(self.lib, "NUMPOINTS")
@property
def LCM(self):
return _CMATRIX.in_dll(self.lib, "LCM")
@property
def PUM(self):
return _BMATRIX.in_dll(self.lib, "PUM")
@property
def CMV(self):
return _VECTOR.in_dll(self.lib, "CMV")
@property
def FUV(self):
return _VECTOR.in_dll(self.lib, "FUV")
@property
def LAUNCH(self):
return c_int.in_dll(self.lib, "LAUNCH")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment