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 (eg mbedtls_cipher_init()) or directly the information requested (eg mbedtls_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 as write() and read(), except there is no equivalent to errno: 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 and 1 for true. The name must be clear: for example, mbdtls_has_foobar_support() will return 1 if support for foobar is present; by contrast, mbedtls_check_foobar_support() will return 0 if support for foobar is present (success) and -1 or a more specific error code if not. All functions named check must follow this rule and return 0 to indicate acceptable/valid/present/etc. Preference should generally be given to check names in order to avoid a mixture of == 0 and != 0 tests.
  • Functions called cmp must return 0 if the two arguments are equal, and if it makes sense, should return -1 or 1 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 as uint32_t.
  • 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() and vsnprintf()
  • the long long and uint64_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 */

Did this help?