Intro
This document describes the preferences used in the mbed TLS code with regards to code formatting, naming conventions, API conventions, coding style, file structure and default content. Of course there are situations where we deviate from this document for 'local' reasons.
Code Formatting
mbed TLS source code files use 4 spaces for indentation and no tabs with a preferred maximum line length of 80 characters.
Every code statement should be on its own line. Statement such as below should be avoided.
if( a == 1 ) { b = 1; do_function( b ); }
if( a == 1 ) do_function( a );
Space placement
mbed TLS uses a non-standard space placement throughout the code, where there is no space between a function name and the opening parenthesis and all parentheses are separated by one space from their content.
if( ( ret = demo_function( a, b, c ) ) != 0 )
The same goes for function definitions
int demo_function( int a, const unsigned char *value, size_t len )
There are a few exceptions to this rule: the preprocessor directive defined
and casts, as well as arguments for function-like macros.
#if defined(MBEDTLS_HAVE_TIME)
timestamp = (uint32_t) time( NULL );
Braces placement / block declaration
Braces (curly brackets) should be located on a line by themselves at the indentation level of the original block.
if( do >= 1 )
{
if( do == 1 )
{
[code block here]
}
else
{
[alternate code block]
}
}
In case a block is only single source code line, the braces can be omitted if the block initiator is only a single line.
if( do >= 1 )
a = 2;
But not if it is a multi-line initiator.
if( do >= 1 &&
this_big_statement_deserved_its_own_line == another_big_part )
{
a = 2;
}
Related lines / Multi-line formatting and indentation
Multiple related source code lines should be formatted to be visually easily readable. Some examples:
#define GET_UINT32_LE( n, b, i ) \
{ \
(n) = ( (uint32_t) (b)[(i) ] ) \
| ( (uint32_t) (b)[(i) + 1] << 8 ) \
| ( (uint32_t) (b)[(i) + 2] << 16 ) \
| ( (uint32_t) (b)[(i) + 3] << 24 ); \
}
if( my_super_var == second_super_var &&
this_check_will_do != the_other_value )
do_function( ctx, this_is_a_value, value_b,
the_special_var );
void this_is_a_function( context_struct *ctx, size_t length,
unsigned char *result );
Extra parentheses for return and sizeof
Within mbed TLS return statements use parentheses to contain their value.
return( 0 );
Similarly, sizeof expressions always use parentheses even when it is not necessary (when taking the size of an object);
memset( buf, 0, sizeof( buf ) );
Precompiler directives
When using precompiler directives to enable / disable parts of the code, use #if defined
instead of #ifdef
. Add a comment to the #endif
directive if the distance to the opening directive is bigger than a few lines or contains other directives.
#if define(MBEDTLS_HAVE_FEATURE)
[ten lines of code or other directives]
#endif /* MBEDTLS_HAVE_FEATURE */
Naming conventions
Namespacing
All public names (functions, variables, types, enum constants, macros) must start with either MBEDTLS_
or mbedtls_
, usually followed by the name of the module they belong to (and submodule if applicable), followed by a descriptive part. Macros and enum constants are uppercase with underscores; other names are lowercase with underscores. Examples:
mbedtls_x509_crt_parse_file()
mbedtls_aes_setkey_decrypt()
Local names
Static functions and macros that are not in public headers follow the same convention except the initial MBEDTLS_
or mbedtls_
prefix: they start directly with the function name.
Function parameters and local variables need no namespacing. They should use descriptive names unless they're very short-lived or are used for simple looping or are "standard" names (eg p
for a pointer to the current position in a buffer).
Lengths and sizes
By default all lengths and sizes are in bytes (or in number of elements, for arrays). If a name refers to a length or size in bits (as is often the case for key sizes) then the name must explicitly include bit
, for example mbedtls_pk_get_bitlen()
returns the size of the key in bits, while mbedtls_pk_get_len()
returns the size in bytes. In addition, the documentation should always mention explicitly if key sizes are in bits or in bytes.
API conventions
Module contexts
If a module uses a context structure for passing around its state, the module should contain an init()
and free()
function, with the module or context name prepended to it. The init()
function must always return void
. If some initialisation must be done that may fail (eg allocating memory), it should be done in a separate function, usually called setup()
unless a more descriptive name can be found. The free()
function must free any allocated memory within the context, but not the context itself; it must zeroize any data in the context or substructures.
mbedtls_cipher_context_t ctx;
mbedtls_cipher_init( &ctx );
ret = mbedtls_cipher_setup( &ctx, ... );
/* Check ret, goto cleanup on error */
/* Do things, goto cleanup on error */
cleanup:
mbedtls_cipher_free( &ctx );
The goal of separating the init()
and setup()
part is that if you have multiple contexts, you can call all the init()
functions first and then all contexts are ready to be passed to the free()
function in case an error happens in one of the setup()
functions or elsewhere.
Return type
Most functions should return int
, more specifically 0
on success (the operation was successfully performed, the object checked was found acceptable, etc.) and a negative error code otherwise. Each module defines its own error codes, see error.h
for the allocation scheme. Exceptions to this rule:
- Functions that can never fail should either return
void
(egmbedtls_cipher_init()
) or directly the information requested (egmbedtls_mpi_get_bit()
). - Functions that look up some information should return either a pointer to this information or
NULL
if it wasn't found. - Some functions may multiplex the return value, eg
mbedtls_asn1_write_len()
returns the length written on success or a negative error code. This mimics the behaviour of some standard functions such aswrite()
andread()
, except there is no equivalent toerrno
: the return code should be specific enough. - Some internal functions may return
-1
on errors rather than a specific error code; it is then up to the calling function to pick a more appropriate error code if the error is to be propagated back to the user. - Functions whose name clearly indicates a boolean (eg, the name contains "has", "is" or "can") should return
0
for false and1
for true. The name must be clear: for example,mbdtls_has_foobar_support()
will return1
if support for foobar is present; by contrast,mbedtls_check_foobar_support()
will return0
if support for foobar is present (success) and-1
or a more specific error code if not. All functions namedcheck
must follow this rule and return0
to indicate acceptable/valid/present/etc. Preference should generally be given tocheck
names in order to avoid a mixture of== 0
and!= 0
tests. - Functions called
cmp
must return0
if the two arguments are equal, and if it makes sense, should return-1
or1
to indicate which argument is greater.
Limited use of in-out parameters
Function should avoid in-out parameters for length (multiplexing buffer size on entry with length used/written on exit) since they tend to impair readability. For example:
mbedtls_write_thing( void *thing, unsigned char *buf, size_t *len ); // no
mbedtls_write_thing( void *thing, unsigned char *buf, size_t buflen,
size_t *outlen ); // yes
In-out parameters may however be used for functions that receive a pointer to some buffer and update it after parsing from or writing to that buffer. For example:
mbedtls_asn1_get_int( unsigned char **p,
const unsigned char *end,
int *value );
In that case, the end
argument should always point to one past the one of the buffer on entry.
Also, contexts are usually in-out parameters, which is OK.
Const correctness
Function declarations should keep const correctness in mind when declaring function arguments. Arguments that are pointers and are not changed by the functions should be marked as such.
int do_calc_length( const unsigned char *str )
Coding style
Restricted C99
The code uses a restricted version of the C99 ISO standard. The only features from C99 (with comparison to C89 / ANSI C) used are:
- the
stdint.h
header for fixed-width types such asuint32_t
. - the
stdbool.h
header for thebool
type - the
inline
keyword (though we use__inline
instead with some compilers known not to support it, such as armcc 5). - one-line comments with
//
- bounded functions
snprintf()
andvsnprintf()
- the
long long
anduint64_t
types - to be pedantic, some string literals that are longer than the length that C89 compilers are required to support
- to be really pedantic, trailing comas in enumerator lists.
As a result variables have to be defined at the top of the code block. In mbed TLS most variables are even defined at the top of the function block.
In the future we might start using more C99 (or even C11) features as support for those standard expands in common compilers.
Proper argument and variable typing
Function arguments and variables should be properly typed. Specifically int and size fields should be able to hold their maximum length in a platform independent way. For buffer length this almost always means the use of size_t
.
For values that should not be negative, use unsigned variables. Do keep the type in mind when building loops with unsigned variables.
Goto
Use of goto
is allowed in functions that have to do cleaning up before returning from the function even when an error has occurred. It can also be used to exit nested loops. In other cases the use of goto
should be avoided.
Exit early / prevent nesting
Structure functions to exit or goto the exit code as early as possible. This prevents nesting of code blocks and improves readability of the code.
External function dependencies
mbed TLS code should minimize use of external functions. Standard libc functions are allowed but should be documented in the KB article on external dependencies.
Minimize code based on precompiler directives
In order to be able to minimize the code size and external dependencies, the availability of modules and module functionality is controlled by precompiler directives located in config.h
. Each module should have at least its own module define for enabling / disabling the module altogether. Other files using the module header should only include the header file it the module is actually available.
Since often systems that use mbed TLS do not have a filesystem, functions specifically using the filesystem should be contained in MBEDTLS_FS_IO
directives.
Minimize use of macros
The use of macros should be avoided unless readability actually improves with use of the macro or code size is drastically impacted otherwise.
The following define actually makes the code using it easier to read.
#define GET_UINT32_LE( n, b, i ) \
{ \
(n) = ( (uint32_t) (b)[(i) ] ) \
| ( (uint32_t) (b)[(i) + 1] << 8 ) \
| ( (uint32_t) (b)[(i) + 2] << 16 ) \
| ( (uint32_t) (b)[(i) + 3] << 24 ); \
}
Clear security relevant memory after use
Memory that contains security relevant information should be zeroized after use and before being released to be reused. The function mbedtls_zeroize()
should be used for that purpose in order to prevent unwanted compiler optimization.
Clear / free what you made
The module that allocated a piece of heap memory is also responsible for releasing it later on unless explicitly documented in the function definition in the header file.
Module self_test()
Each module should have a self-test function (between a check for MBEDTLS_SELF_TEST). This function should test basic module sanity, but should stay away from performing time consuming tests.
Doxygen doc formatting
The header files should be documented with Doxygen-style code comments. Using the '\' character as the separator.
/**
* \brief A useless function just being present for documentation purposes.
* When calling this function, do not expect something to happen.
*
* \note This function has no influence on code security.
*
* \param buf Buffer to ignore
* \param len Length of buffer
*
* \return 0 if succesfully ignored, a module specific error code otherwise.
*/
Loose coupling interfaces
Each module should keep loose coupling with external modules and functions in mind. The use of flexible function pointers are preferred over hard functions calls in cases where a developer could want to replace part of the code with a local version.
Generic file structure
Header files
Header files are structured as follows:
- License Part (GPL)
- Header file define for MBEDTLS_ {MODULE_NAME} _H
#ifndef MBEDTLS_AES_H
#define MBEDTLS_AES_H
- Includes
- Public defines (Generic and error codes) and portability code
- C++ wrapper for C code
#ifdef __cplusplus
extern "C" {
#endif
- Public structures
- Function declarations
- C++ end wrapper
#ifdef __cplusplus
}
#endif
- Header file end define
#endif /* MBEDTLS_AES_H */
Source files
Source files are structured as follows:
- License Part (GPL)
- Comments on possible standard documents used
- Config include and precompiler directive for module
#if !defined(MBEDTLS_CONFIG_FILE)
#include "mbedtls/config.h"
#else
#include MBEDTLS_CONFIG_FILE
#endif
#if defined(MBEDTLS_AES_C)
- Includes
- Private local defines and portability code
- Static variables
- Function definitions
- Precompiler end directive for module
#endif /* MBEDTLS_AES_C */