原文 https://tls.mbed.org/kb/development/mbedtls-coding-standards
文章目录
- 介绍 (Intro)
- 代码格式 (Code Formatting)
- 空格布局 (Space placement)
- 大括号放置/块声明 (Braces placement / block declaration)
- 关联行/多行格式化和缩进(Related lines / Multi-line formatting and indentation)
- 返回和 sizeof 的括号 (Extra parentheses for return and sizeof)
- 预编译指令 (Precompiler directives)
- 命名约定 (Naming conventions)
- API约定 (API conventions)
- 模块上下文 (Module contexts)
- 返回类型 (Return type)
- 限制使用 in-out 参数 (Limited use of in-out parameters)
- Const 校正 (Const correctness)
- 编码风格 (Coding style)
- Restricted C99
- 正确的参数和变量类型 (Proper argument and variable typing)
- 关于 goto (Goto)
- 尽早退出/防止嵌套 (Exit early / prevent nesting)
- 外部函数依赖关系 (External function dependencies)
- 通过预编译指令最小化代码 (Minimize code based on precompiler directives)
- 尽量减少宏的使用(Minimize use of macros)
- 及时清除安全相关内存 (Clear security relevant memory after use)
- 关于释放内存 (Clear / free what you made)
- 模块自测 (Module self_test())
- Doxygen (Doxygen doc formatting)
- 接口间解耦合 (Loose coupling interfaces)
- 文件结构 (Generic file structure)
介绍 (Intro)
本文档描述了mbed TLS代码中使用的有关代码格式、命名约定、API约定、编码风格、文件结构和默认内容的首选项。当然,在某些情况下,我们会因为某些原因而偏离本文档。
代码格式 (Code Formatting)
mbed TLS源代码文件使用4个空格进行缩进,没有首选的最大行长度为80个字符的制表符。
每个代码语句都应该在自己的行上。应该避免使用下面这样的语句。
if( a == 1 ) { b = 1; do_function( b ); }
if( a == 1 ) do_function( a );
空格布局 (Space placement)
mbed TLS在整个代码中使用非标准空格布局,其中函数名和开始圆括号之间没有空格,所有圆括号与内容之间用一个空格分隔。
if( ( ret = demo_function( a, b, c ) ) != 0 )
函数定义也是如此
int demo_function( int a, const unsigned char *value, size_t len )
这个规则有几个例外:定义和强制转换的预处理指令,以及类似函数的宏的参数。
#if defined(MBEDTLS_HAVE_TIME)
timestamp = (uint32_t) time( NULL );
大括号放置/块声明 (Braces placement / block declaration)
大括号应该单独一行并与原始块保持同样的缩进。
if( do >= 1 )
{
if( do == 1 )
{
[code block here]
}
else
{
[alternate code block]
}
}
如果一个块只有一行源代码并且块判断条件只有一行,可以省略大括号。
if( do >= 1 )
a = 2;
但是当块判断条件大于一行,则大括号不能省略。
if( do >= 1 &&
this_big_statement_deserved_its_own_line == another_big_part )
{
a = 2;
}
关联行/多行格式化和缩进(Related lines / Multi-line formatting and indentation)
应该格式化多个相关的源代码行,使其在视觉上易于阅读。例如:
#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 );
返回和 sizeof 的括号 (Extra parentheses for return and sizeof)
在mbed TLS返回语句中使用括号来包含返回值。
return( 0 );
类似地,sizeof
表达式总是使用括号,即使这不是必须的;
memset( buf, 0, sizeof( buf ) );
预编译指令 (Precompiler directives)
当使用预编译指令启用/禁用部分代码时,使用 #if
而不是 #ifdef
。如果到开始指令的距离大于几行或包含其他指令,要在 #endif
指令后添加注释。
#if define(MBEDTLS_HAVE_FEATURE)
[ten lines of code or other directives]
#endif /* MBEDTLS_HAVE_FEATURE */
命名约定 (Naming conventions)
命名空间 (Namespacing)
所有公共名称(函数、变量、类型、枚举常量、宏)都必须以 MBEDTLS_
或 mbedtls_
开头,通常后跟它们所属的模块的名称(如果适用的话,还包括子模块),后跟描述性部分。宏和枚举常量用大写加下划线; 其他名称用小写加下划线。例如:
mbedtls_x509_crt_parse_file()
mbedtls_aes_setkey_decrypt()
内部命名方式 (Local names)
除 MBEDTLS_
或 mbedtls_
前缀外,不在公共头文件中的静态函数和宏可以直接从函数名开始。
函数参数和局部变量不需要命名空间。它们应该使用描述性名称,除非它们非常短或者用于简单的循环,或者是约定俗成的名称(例如 p
表示指向缓冲区中当前位置的指针)。
长度和大小 (Lengths and sizes)
默认情况下,所有长度和大小都以字节(或数组的元素数量)为单位。如果名称引用以位为单位的长度或大小(这通常是 key 大小的情况),则名称必须显式包含位,例如mbedtls_pk_get_bitlen()
以位为单位返回键的大小,而 mbedtls_pk_get_len()
以字节为单位返回大小。此外,如果 key 的大小是以位或字节为单位的,文档应该总是明确地提到。
API约定 (API conventions)
模块上下文 (Module contexts)
如果模块使用上下文结构传递其状态,则模块应该包含 init()
和 free()
函数,并在其前面加上模块或上下文名称。init()
函数必须始终返回 void
。如果必须执行一些可能会失败的初始化(例如分配内存),那么应该在一个单独的函数中执行,通常称为 setup()
,除非能找到一个更具描述性的名称。free
函数的作用是:释放上下文中分配的内存,但不释放上下文本身;它必须将上下文中或子结构中的任何数据归零。
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 );
将 init()
和 setup()
部分分开的目的是,如果有多个上下文,可以首先调用所有的 init()
函数,然后将所有上下文都准备好传递给 free()
函数,以防其中一个 setup()
函数或其他地方发生错误。
返回类型 (Return type)
大多数函数应该返回 int
,更确切地说应该返回 0
,否则返回一个负值错误代码。每个模块都定义了自己的错误代码,分配方案见 error.h 。例外情况:
- 不可能失败的函数应该返回
void
(例如mbedtls_cipher_init()
)或直接返回请求的信息(例如mbedtls_mpi_get_bit()
)。 - 查找某些信息的函数应该返回指向该信息的指针,如果没有找到则返回
NULL
。 - 有些函数可能会使返回值复杂化,例如
mbedtls_asn1_write_len()
返回在成功或负错误代码上写入的长度。这模拟了一些标准函数的行为,比如write()
和read()
,只是没有与errno
等价的函数:返回代码应该足够具体。 - 一些内部函数可能在错误时返回
-1
,而不是特定的错误代码;然后,如果要将错误传播回用户,则由调用函数选择更合适的错误代码。 - 如果函数的名称明确表示布尔值(例如,名称包含“has”、“is”或“can”),则返回
0
表示 false,返回1
表示 true。名称必须清楚:例如,如果存在对 foobar 的支持,mbdtls_has_foobar_support()
将返回1
;相反,如果支持 foobar (成功),mbedtls_check_foobar_support()
将返回0
;如果不支持 foobar,则返回-1
或更具体的错误代码。所有名为 check 的函数必须遵循这条规则,并返回0
来表示可接受/有效/存在/等等。为了避免混合使用== 0
和!= 0
测试,通常应该优先选择检查名称。 - 如果两个参数相等,则调用
cmp
的函数必须返回0
,如果有意义,则应该返回-1
或1
,以指示哪个参数更大。
限制使用 in-out 参数 (Limited use of in-out parameters)
函数应该避免长度的 in-out 参数(输入缓存大小/写入的长度),因为它们可能会降低可读性。例如:
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 参数可以用于接收指向某个缓冲区的指针的函数,并在从该缓冲区解析或写入缓冲区后更新该指针。例如:
mbedtls_asn1_get_int( unsigned char **p,
const unsigned char *end,
int *value );
在这种情况下,end 参数应该始终指向入口时缓冲区的前面。
此外,上下文通常是 in-out 参数,这是可以的。
Const 校正 (Const correctness)
函数声明时应牢记 const 校正。作为指针且不被函数更改的参数应该标记为 const 指针。
int do_calc_length( const unsigned char *str )
编码风格 (Coding style)
Restricted C99
代码使用了C99 ISO标准的 Restricted 版本。C99(与C89 / ANSI C相比)的特点是:
- 使用
stdint.h
头文件固定宽度类型(如uint32_t
)。 - 增加 描述
bool
类型的头文件stdbool.h
。 inline
关键字(尽管我们使用__inline
替换了一些不支持它的编译器,比如armcc 5)。- 带有
//
的一行注释 - 有界函数
snprintf()
和vsnprintf()
long long
和uint64_t
类型- 字符串最大长度超过了C89编译器需要支持的长度,
- 在枚举列表中以逗号结尾。
因此,必须在代码块的顶部定义变量。在 mbed TLS 中,大多数变量甚至在函数块的顶部定义。
将来,我们可能会开始使用更多的C99(甚至C11)特性,因为对这些标准的支持将在通用编译器中扩展。
正确的参数和变量类型 (Proper argument and variable typing)
函数参数和变量应该正确键入。特别是 int 和 size 字段应该能够以独立于平台的方式保持它们的最大长度。对于缓冲区长度,这几乎总是意味着使用 size_t
。
对于不应该为负的值,使用无符号变量。在使用无符号变量构建循环时,一定要保持该类型。
关于 goto (Goto)
函数中允许使用 goto
,用于出现错误在函数返回之前进行清理。它还可以用来退出嵌套循环。在其他情况下,应该避免使用 goto
。
尽早退出/防止嵌套 (Exit early / prevent nesting)
结构化函数,以便尽早退出或转到退出代码。这可以防止代码块嵌套,并提高代码的可读性。
外部函数依赖关系 (External function dependencies)
mbed TLS 代码应该尽量减少使用外部函数。标准的 libc 函数是允许的,但应该在这里记录 What external dependencies does mbed TLS rely on?
通过预编译指令最小化代码 (Minimize code based on precompiler directives)
为了能够最小化代码大小和外部依赖,模块和模块功能的可用性由 config.h
中的预编译指令控制。每个模块应该至少定义自己的模块,以便启用/禁用该模块。使用模块头的其他文件应该只包含模块实际可用时的头文件。
由于使用 mbed TLS 的系统通常没有文件系统,因此应该在 MBEDTLS_FS_IO
指令中包含专门使用文件系统的函数。
尽量减少宏的使用(Minimize use of macros)
应该避免使用宏,除非可读性实际上随着宏的使用而提高,或者如果不使用宏的话代码大小将受到极大的影响。
下面的定义确实让使用它的代码更容易阅读。
#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)
包含安全相关信息的内存在使用后和释放重用之前应该置零。函数 mbedtls_zeroize()
就是用于此目的,以防止不必要的编译器优化。
关于释放内存 (Clear / free what you made)
分配堆内存块的模块还负责稍后释放堆内存块,除非在头文件中的函数定义中有明确的说明。
模块自测 (Module self_test())
每个模块都应该有一个 self-test 函数(在检查 MBEDTLS_SELF_TEST 之间)。这个函数应该测试基本模块的完整性,但是应该避免执行耗时的测试。
Doxygen (Doxygen doc formatting)
头文件应该用 doxygen 风格的代码注释。使用 ‘’ 字符作为分隔符。
/**
* \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)
每个模块都应该与外部模块和函数保持松散耦合。在开发人员可能希望用本地版本替换部分代码的情况下,使用灵活的函数指针优于使用硬函数调用。
文件结构 (Generic file structure)
头文件 (Header files)
头文件的结构如下:
- 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)
源文件的结构如下:
- 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 */