Mbed TLS coding standards

Intro

This document describes Mbed TLS preferences for code formatting, naming conventions, API conventions, coding style, file structure, and default content in C code. For build and test scripts, see the script coding standards.

Note: There are situations where we deviate from this document for ‘local’ reasons.

Code Formatting

Mbed TLS source code files use 4 spaces for indentation, not tabs, with a preferred maximum line length of 80 characters.

Every code statement should be on its own line.

Avoid statements like this:

    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 all parentheses are separated by one space from their content:

    if( ( ret = demo_function( a, b, c ) ) != 0 )

The same applies to function definitions:

    int demo_function( int a, const unsigned char *value, size_t len )

There are a few exceptions to this rule. This includes 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 and block declaration

Braces (curly brackets) should be located on a line by themselves at the indentation level of the original block:

    if( val >= 1 )
    {
        if( val == 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( val >= 1 )
        a = 2;

But not if it is a multi-line initiator:

    if( val >= 1 &&
        this_big_statement_deserved_its_own_line == another_big_part )
    {
        a = 2;
    }

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 ) );

Formatting of lists

When a function or macro call doesn’t fit on a single line, put one argument per line or a sensible grouping per line. For example, in the snippet below, input_buffer and input_size are on a line of their own even though the call could fit on two lines instead of three if they were separated:

    function_with_a_very_long_name( parameter1, parameter2,
                                    input_buffer, input_size,
                                    output_buffer, output_size );

Lists of items other than function arguments should generally have one item per line. Exceptions:

  • It’s ok to write simple structure initializers on a single line.

  • In an array initialized with numerical data, pack a sensible and consistent number of items per line. The number of items per line should be a power of 2 to faciliate counting.

In lists of items such array initializers and enum definitions, do include the optional comma after the last element. This simplifies merging, reordering, etc.

typedef enum foo
{
    FOO_1,
    FOO_2,      // <- do put a comma here
}

Exceptions: you can and omit the trailing comma in structure initializers that fit on one line, or if the last element must always remain last (e.g. a null terminator).

Preprocessor directives

When using preprocessor directives to enable or 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

Name spacing

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:

    mbedtls_x509_crt_parse_file()
    mbedtls_aes_setkey_decrypt()

Note that all externally linked functions must have a name starting with mbedtls_ to avoid link-time conflicts, even if they are not declared in a public header. This also applies to global variables (which should be used very sparingly).

Exception: code implementing the PSA crypto API uses the PSA_ and psa_ prefixes. This includes official APIs as well as draft APIs that are on the PSA standards track. API extensions which are meant to remain specific to Mbed TLS, and internal functions, should use the MBEDTLS_/mbedtls_ prefix, however there are many existing cases of using the PSA_/psa_ prefix.

Exception: macros may have lowercase names if they behave exactly like normal variables and functions. In particular, function-like macros may have lowercase names only if they expand to an expression that evaluates each argument exactly once. Even in those cases, uppercase names are strongly preferred and new code should not use lowercase macro names for anything other than function replacement (like #define mbedtls_printf printf).

Local names

Static functions can use the same naming scheme as non-static functions (mbedtls_MODULE_frobnicate() or psa_frobnicate()). This is convenient if they are made non-static later, for example for testing. They can also have shorter names if it’s convenient. Keeping the module name (MODULE_frobnicate()) as a prefix makes it slightly easier to follow call stacks across modules, but it is not compulsory.

Macros defined only in the library directory should follow the same naming scheme as non-static functions (MBEDTLS_MODULE_FOO or PSA_FOO). They can use shorter names (omitting MBEDTLS_, the module name, or both) if it’s convenient. However, avoid names without underscores as some embedded platforms define short macro names in their system headers. Exception: macros defined in headers used by alternative implementations or PSA drivers (including headers that they include) must start with MBEDTLS_ or PSA_.

Function parameters and local variables need no name spacing. They should use descriptive names unless they’re very short-lived or are used for simple looping or are “standard” names (such as 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.

size should refer to the capacity of a buffer, and length to the length of the contents. Most of the time these are interchangeable. A typical exception is when the output is written in a buffer, but the exact length is not known by the caller.

Modules: bignum_core, bignum_mod and bignum_mod_raw

Generic conventions:

  • mbedtls_mpi_uint * input operands should be named by capital letters starting at the beginning of the alphabet (A, B, C, …).

  • mbedtls_mpi_uint operands in turn should be named by lower case letters starting at the beginning of the alphabet (a, b, c)

  • For the result X or x should be used depending on the type.

  • N is generally used for the modulus.

  • T is generally used for a temporary work area given to a function.

  • An exception from this convention is where the naming of function parameters and local variables follows the literature (e.g. Handbook of Applied Cryptography)

Length parameters:

  • For length of mbedtls_mpi_uint * buffers we use limbs.

  • Length parameters are qualified if possible (e.g. input_length or A_limbs)

API conventions

This section applies fully to classic mbedtls_xxx() APIs and mostly to the newer psa_xxx() APIs. PSA have their own conventions described in the PSA Crypto API specification which take precedence in case of conflicts.

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 initialization must be done that may fail (such as allocating memory), it should be done in a separate function, usually called setup(). The free() function must free any allocated memory within the context, but not the context itself. It must set to zero 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 (such as mbedtls_cipher_init()) or directly the information requested (such as 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.

  • PSA functions that can fail return a psa_status_t value.

  • Some functions may multiplex the return value, such as mbedtls_asn1_write_len() returns the length written on success or a negative error code. This mimics the behavior 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 (such as, 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

For PSA functions, input buffers have a size_t xxx_size parameter after the buffer pointer, and output buffers have a size_t xxx_size parameter for the buffer size followed by a size_t *xxx_length parameter for the output length. This convention is also preferred in new mbedtls_xxx code, but older modules often use different conventions.

You can use in-out parameters for functions that receive a pointer to some buffer, and update it after parsing from or writing to that buffer:

    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 acceptable.

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

ISO C99

The code uses the C99 ISO standard.

Proper argument and variable typing

Type function arguments and variables properly. Specifically, the int and size fields hold their maximum length in a platform-independent way. For buffer length, this almost always means using size_t.

For values that can’t be negative, use unsigned variables. 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 and prevent nesting

Structure functions to exit or goto the exit code as early as possible. This prevents nesting of code blocks and improves code readability.

Most functions that need cleanup have a single cleanup block at the end. The label for this block can be cleanup: or exit: (or error: if the block is skipped on success); follow the established convention when extending an existing module. Code that uses bignum must use cleanup: for the sake of MBEDTLS_MPI_CHK.

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.

Preprocessor

Minimize code based on preprocessor directives

To minimize the code size and external dependencies, the availability of modules and module functionality is controlled by preprocessor directives located in mbedtls/mbedtls_config.h. Each module should have at least its own module define for enabling or disabling the module altogether. Other files using the module header should only include the header file if the module is actually available.

Since often systems that use Mbed TLS do not have a file system, functions specifically using the file system should be contained in MBEDTLS_FS_IO directives.

Conditional compilation hygiene

The span of a conditional compilation directive should generally be a sequence of C instructions or declarations.

If necessary, the span can be an expression or a sequence of expressions. For example, this is acceptable:

static char self_test_key = {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
#if !defined(SUPPORT_128_BIT_KEYS)
    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
#endif
};

This is acceptable, but deprecated:

    if(
#if defined(MBEDTLS_USE_PSA_CRYPTO)
        psa_condition( )
#else
        legacy_condition( )
#endif
      )
        do_stuff( );

Such code is generally more readable with an intermediate variable, thus the following style is preferred:

#if defined(MBEDTLS_USE_PSA_CRYPTO)
    const int want_stuff = psa_condition( );
#else
    const int want_stuff = legacy_condition( );
#endif
    if( want_stuff )
        do_stuff( );

Do not put partial instructions in a conditionally compiled span. For example, do not write the code above like this:

#if defined(MBEDTLS_USE_PSA_CRYPTO)     // NO!
    if( psa_condition( ) )              // NO!
#else                                   // NO!
    if( legacy_condition( ) )           // NO!
#endif                                  // NO!
        do_stuff( );                    // NO!

Having two instances of if in the source code, but only one actually compiled, is confusing both for humans and for tools such as indenters and linters.

Exception: the following idiom, with a chain of if-else-if statements, is accepted.

#if defined(MBEDTLS_FOO)
    if( is_a_foo( type ) )
        process_foo( type, data );
    else
#endif /* MBEDTLS_FOO */
#if defined(MBEDTLS_BAR)
    if( is_a_bar( type ) )
        process_bar( type, data );
    else
#endif /* MBEDTLS_BAR */
    {
        return ERROR_NOT_SUPPORTED;
    }

Never have unbalanced braces in a conditionally compiled span. Exception: public headers can, and must, have extern "C" { and } guarded by #ifdef __cplusplus.

Minimize use of macros

Only use macros if they improve readability or maintainability, preferably both. If macros seem necessary for maintainability but hinder readability, consider generating code from a Python script instead.

If possible, use the C core language rather than macros. For example, if an expression is used often, a static inline function is better because it provides type checks, allows the compiler to keep the function call when optimizing for size, and avoids problems with arguments evaluated more than once. However, if the expression needs to be a compile-time constant when its parameters are, this is a good reason to use a macro.

Macro definition hygiene

When the code contains a macro call MBEDTLS_FOO( x, y ), it should behave as much as possible as if MBEDTLS_FOO was a function. Deviate from this only to the extent necessary to make the macro practical.

If the arguments of a macro are C expressions (they usually are), put parentheses around the argument in the expansion. For example:

#define FOO_SIZE( bits ) ( ( (bits) + 7 ) / 8 + 4 )

The expansion contains ( (bits) + 7 ), not bits + 7, so that a call like FOO_SIZE( x << 3 ) is parsed correctly. As an exception, it’s ok to omit parentheses if the argument is directly passed to a function argument (or comma operator): #define A( x ) f( x, 0 ) is acceptable.

If the expansion of a macro is a C expression, put parentheses around the expansion. Continuing the example above, this is so that a call like FOO_SIZE( x ) * 2 is parsed correctly. As an exception, it’s ok to omit parentheses if the expansion is a function call or other highest-precedence operator: #define A( x ) f( x, 0 ) is acceptable.

If a macro expands to a statement, wrap it in do { ... } while( 0 ) so that it can be used in contexts that expect a single statement. For example:

#define MBEDTLS_MPI_CHK(f)       \
    do                           \
    {                            \
        if( ( ret = (f) ) != 0 ) \
            goto cleanup;        \
    } while( 0 )`

The expansion is not just if( ( ret = (f) ) != 0 ) goto cleanup because that would not work in a context like if( condition ) MBEDTLS_MPI_CHK( f( ) ); else ++x; (the else would get attached to the wrong if).

Follow the expression paradigm or the statement paradigm if possible. Other paradigms are permitted if necessary, for example a macro that expands to an initializer such as

#define MBEDTLS_FOO_INIT {0, {0, 0}}

The expansion of a macro must not contain unbalanced parentheses, brackets or braces.

In macro expansions, do not make assumptions about the calling context, for example do not assume that a particular variable is defined. Exception: variable names with a very strong convention in the code base, like the ret example above which is systematically used for status codes in the classic mbedtls API. If the macro needs an intermediate variable, give it a long name that won’t clash with anything else, but strongly consider using a function instead.

Clear security-relevant memory after use

Memory that contains security-relevant information should be set to zero after use, and before being released to be reused. Use the function mbedtls_platform_zeroize() to prevent unwanted compiler optimization.

Clear and 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 stay away from performing time-consuming tests.

Doxygen documentation formatting

The header files should be documented with Doxygen-style code comments. Use the ‘’ character as a separator.

Function

Description

\brief

A useless function present for documentation purposes.

\note

This function has no influence on code security.

\param buf

Buffer to ignore.

\param len

Length of buffer.

\return

0 if successfully ignored, otherwise, a module-specific error code.

Loose coupling interfaces

Each module should keep loose coupling with external modules and functions in mind. Use flexible function pointers over hard functions calls in cases where you want to replace part of the code with a local version.

Generic file structure

Header files

Structure header files as follows:

  • Brief description of the file.

  • Copyright notice and license indication.

  • Header file define for MBEDTLS_ {MODULE_NAME} _H:

#ifndef MBEDTLS_AES_H
#define MBEDTLS_AES_H
  • Includes. Always include <mbedtls/build_info.h> before anything that might depend on the compile-time configuration.

  • Public defines (Generic and error codes) and portability code.

  • C++ wrapper for C code:

#ifdef __cplusplus
extern "C" {
#endif
  • For modules with optional alternative implementations, check for module specific structures:

#if !defined(MBEDTLS_AES_ALT)
  • Public structures that can have alternative implementations.

  • For modules with optional alternative implementations, include the alternative header file:

#else  /* MBEDTLS_AES_ALT */
#include "aes_alt.h"
#endif /* MBEDTLS_AES_ALT */
  • Public structures that should not have alternative implementations.

  • Function declarations.

  • C++ end wrapper:

#ifdef __cplusplus
}
#endif
  • Header file end define:

#endif /* MBEDTLS_AES_H */

Source files

Source files are structured as follows:

  • Brief description of the file.

  • Copyright notice and license indication.

  • Comments on possible standard documents used.

  • Preprocessor directive for module:

#if defined(MBEDTLS_AES_C)
  • Includes. All library source files start by including "common.h".

  • If applicable, preprocessor directive for alternative implementation:

#if !defined(MBEDTLS_AES_ALT)
  • Private local defines and portability code.

  • Function definitions.

  • If applicable, preprocessor directive for marking the end of an alternative implementation:

#endif /* !MBEDTLS_AES_ALT */
  • Preprocessor directive for selftest (where applicable):

#if defined(MBEDTLS_SELF_TEST)
  • Self-test test vectors.

  • Self test implementation.

  • Preprocessor directive for marking the end of self tests (where applicable):

#endif /* MBEDTLS_SELF_TEST */
  • Preprocessor directive for marking the end of a module:

#endif /* MBEDTLS_AES_C */