This implementation of the ElectionGuard SDK serves to showcase the API provided by the SDK. For more details about that API, see the API Reference.
To enable cross-platform building, we use cmake. Any generator should work, but we describe here how build using Visual Studio and using a Unix-like shell.
As long as you have the C++ CMake tools for Windows installed,
cmake should automatically integrate with Visual Studio.
This means you can build the project from the IDE, for example by
selecting Build > Build All
in the menu.
First create a build directory and configure the build.
mkdir build
cmake -S . -B build
To build the SDK static library libelectionguard.a
, run
cmake --build build
Currently you can exercise the SDK by running the example client. We include a cmake test to do so automatically. You can also execute the client directly to better examine the output it produces.
Warning
The current implementation allocates most things statically, leading to a large stack. This can cause stack-overflows.
The size of the stack mostly depends on the value of MAX_TRUSTEES
in
include/max_trustees.h
, so one way to fix the problem is to reduce
that number and recompile.
You can also increase the stack size, for example using ulimit.
In addition, this causes issues with valgrind. The error messages
are usually pretty helpful, and setting --main-stacksize
and
--main-stackframe
according to its reccomendations usually fixes the issue.
To build and execute an example client of the SDK, run the tests,
for example by selecting Test > Run CTests for ElectionGuard SDK
.
The example client can also be built as a standalone project if it is configured with the location of the SDK, but this is not covered here.
To build and run an example client of the SDK, run the tests:
cmake --build build --target test
Alternatively you can build the client as a stand-alone project. Create a separate build directory for the client, configure the build to refer to the built library, and build the client.
mkdir simple_build
ElectionGuard_DIR="$PWD/build/ElectionGuard" cmake -S examples/simple -B simple_build
cmake --build simple_build --target simple
The built binary should be located at simple_build/simple
.
To enable debug builds suitable for running with debuggers like
lldb, set the CMAKE_BUILD_TYPE
cmake variable to
Debug
when configuring. From the command-line, this looks like
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
Some development tools like ccls or cquery use a
JSON file called compile_commands.json
to lookup which build
flags are used to build different files. To produce such a file while
compiling, set the CMAKE_EXPORT_COMPILE_COMMANDS
cmake variable.
From the command-line, this looks like
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
To build the HTML documentation, you will need to have
doxygen installed, as well as python with the
sphinx
and breathe
packages. Then configure your build with
the BUILD_DOCUMENTATION
variable set and rebuild.
Note
Make sure that you’ve initialized git submodules correctly. The theme used for the documentation is in a submodule.
git submodule update --init --recursive
cmake -S . -B build -DBUILD_DOCUMENTATION=ON
cmake --build build
and the documentation will be built in the build/docs/html
directory. You can browse it locally by opening
build/docs/html/index.html
, or by running a local server
Any pointers returned by functions in the SDK are considered to be owned by the caller. This means that the SDK will retain no references to them, and that the caller must free them when they are done.
Any pointers passed to functions in the SDK as arguments are
considered to be borrowed by the function, which means that they will
not be freed by that function, and it is still the responsibility of the
caller to free the pointer. This of course excludes functions whose
purpose is to free an opaque data type, like
KeyCeremony_Trustee_free()
.
This only applies when functions return with a successful status. If a function returns with an error status, the client does not need to free any memory that may have been allocated by the function; it will clean up after itself.
All public functions are prefixed with the name of their “class” or module, capitalized.
There are a few different kinds of types, and they each have their own
naming conventions. The rationale is that for types that we rely on the
fact that they are enums or structs, we should not typedef
them so
that it is clear that they are enums and structs. If that changes, we
will have to go fix it everywhere, which is good, because now we cannot
rely on their representation anymore. Abstract types should be
typedef
ed because we don’t rely on their implementation.
A type whose implementation we want to be hidden from clients. This means that it must be hidden behind a pointer so its size doesn’t need to be known.
Naming convention: uppercase, with their structs suffixed with
_s
.
typedef struct Car_s *Car;
A enum whose values represent possible statuses that we want to return.
Naming convention: prefixed by module or scope, then lowercase, and
no typedef
.
enum Car_status {
CAR_SUCCESS,
CAR_ON_FIRE,
};
A struct whose sole purpose is to allow us to return multiple values, often a status enum and a payload.
Naming convention: prefixed by module or scope, then lowercase, then
_r
, and no typedef
. If only used for a single function, make the
name identical to the function name, then _r
. You can forward
declare in the return type.
struct Car_drive_r Car_drive(Car c);
struct Car_drive_r {
enum Car_status status;
int x;
int y;
};
A type we want to name, but whose implementation need not be hidden. In fact, we might rely on the details of its representation.
Naming convention: all lowercase, no typedef
.
struct model {
int year;
enum color color;
};