Proposal for a libcrypto module style guide / policy
Richard Levitte
levitte at openssl.org
Mon Sep 28 16:50:22 UTC 2020
The code style proposal that we had before didn't fare well and was
deemed incomplete or contradictory. Still, we need something for a
more overall view on how we should design an new API as well as how we
should treat existing APIs.
Here's a proposal for something that I/we hope have grasped all the
diverse feedback that's come our way and can become a good enough
guide.
I currently have it as a separate file, and not committed anywhere,
because I wasn't sure where it should end up, and need ideas.
Something to be noted is the current coding style policy doesn't
have a natural space for higher level items such as whole APIs (as
opposed to individual functions) or modules. My proposal would be to
have a separate document for API / module design (the name of the
attached text is a possible name).
Ideas, thoughts, constructive criticism, acclamations, etc welcome.
Cheers,
Richard
--
Richard Levitte levitte at openssl.org
OpenSSL Project http://www.openssl.org/~levitte/
-------------- next part --------------
OpenSSL modules
===============
A module in the sense described here is a part of OpenSSL functionality, in
particular in libcrypto, not a dynamically loadable module.
A module is recognised by its upper case beginning of public function names.
For example, "RAND_bytes" is a function in the RAND module.
In the rest of this text, '{NAME}' is used to represent the module name.
For working with entire modules, we have three cases to consider:
1. Existing modules
2. New modules
3. Function arguments in new modules
[
note: there are core "modules" as well, which aren't quite the same.
CRYPTO and OSSL_PROVIDER come to mind.
]
Existing modules
================
>From time to time it is necessary to extend an existing module with new
functions. To make such functions consistent with the API of that module,
look at similar or related functions in the same module, otherwise follow
the general rules for adding functions in a new module as described below.
New modules
===========
OpenSSL modules may have different levels of complexity:
1. Simple
2. Class
3. Class, dynamic
Simple
------
The absolutely simplest don't have an associated type, and simply have
functions of this form:
rettype {NAME}_do_something([args])
Otherwise, still fairly simple are those with an associated type that has
the same name as the module, but nothing more:
{NAME} *{NAME}_new([args])
rettype {NAME}_get_something({NAME} *,[args])
rettype {NAME}_set_something({NAME} *,[args])
rettype {NAME}_do_something({NAME} *,[args])
void {NAME}_free({NAME} *)
Class
-----
Somewhat more complex modules have multiple backend implementations, much
like C++ classes have virtual methods. For those, there are two types,
{NAME} and {NAME}_METHOD, where {NAME} holds execution data, while
{NAME}_METHOD holds function pointers to a backend implementation.
Such modules should use the following pattern:
{NAME} *{NAME}_new({NAME}_METHOD *, [args])
rettype {NAME}_get_something({NAME} *,[args])
rettype {NAME}_set_something({NAME} *,[args])
rettype {NAME}_do_something({NAME} *,[args])
void {NAME}_free({NAME} *)
Class, dynamic
--------------
Some more complex modules are also dynamic, in that they get their
implementation from diverse places, and referring to those implementations
isn't as simple as using a method structure pointer directly. It's often
more suitable that the constructor finds the method structure implicitly,
from a given implementation name or an object that can be used to derive a
suitable name from.
The constructor for such a module typically relies on a factory pool, such
as OPENSSL_CTX (OPENSSL_CTX is a bag of diverse core library things, and is
generally seen as the libcrypto library state. With constructors, it is
used in the role of a factory pool).
The constructor pattern should look like this:
{NAME} *{NAME}_new(OPENSSL_CTX *libctx, const char *name, [args])
{NAME} *{NAME}_new(OPENSSL_CTX *libctx, OBJTYPE *object, [args])
The first variant is a straightforward construct by name, the second would
rather derive the name from another related type.
The rest of such a module's API should like as in "Class" above.
Function arguments in new modules
=================================
For all of the above patterns, the remaining argument list (seen as '[args]'
above) should be put into order of importance. Deciding the relative
importance of arguments is necessarily somewhat subjective, but the
following guideline ordering of the arguments should be considered:
1) additional context arguments
If multiple contexts are necessary then they should be in order of most
general to most specific. In some cases (typically with an OPENSSL_CTX),
a context may be NULL to indicate a default context should be used.
[ This should be *very* rare ]
2) Data transfer parameters (these are parameters used to convert or copy
some sort of input data to some kind of output data).
a) output / destination data parameters
b) input / source data parameters
3) Required arguments
4) Optional arguments
Here optional are arguments that are meant to be used in certain
circumstances only and therefore may be "NULL" or empty.
5) Function pointers to callback functions and associated callback data
pointer, in pairs.
In function prototypes, include parameter names with their data types.
Although this is not required by the C language, it is preferred in OpenSSL
because it is a simple way to add valuable information for the reader. The
name in the prototype declaration should match the name in the function
definition.
More information about the openssl-project
mailing list