模块契约PEM

错误

PEM 错误使用 xrt.pem 域:

  • XPEM_ERROR_BOUNDARY:边界缺失、嵌套或行格式错误;
  • XPEM_ERROR_LABEL:标签非法或开始、结束标签不一致;
  • XPEM_ERROR_BODY:正文不是规范 Base64;
  • XPEM_ERROR_NOT_FOUND:目标标签不存在。

结构错误在适用时把 offset=<字节偏移> 写入错误数据,供上层错误映射和 C 日志定位。

裁剪与依赖

pem 只依赖 codec_base64;不依赖 ASN.1、加密、文件或网络。只处理二进制与文本的封装关系,证书和密钥语义留给上层模块。

借用式遍历

xrtPemRead 允许块前后存在说明文本,并识别 LF、CRLF 和 CR。开始与结束标签必须精确匹配;嵌套开始边界、缺失结束边界、非法标签和边界尾部文本都会被拒绝。返回 XPEM_DONE 表示正常结束,不设置错误。

游标和块只在成功时更新。Raw 精确覆盖本次消费的块,从开始边界到结束边界的行尾;原输入必须在所有借用视图使用完之前保持有效,输入不要求以零字节结尾。

查找与解码

xrtPemFind 按出现顺序查找第一个标签完全相同的块。xrtPemDecode 支持查询长度和调用方缓冲;xrtPemDecodeNew 返回由 xrtFree 释放的字节,并额外保留一个不计入长度的末尾零字节。

正文使用严格、规范的 Base64 解码,只忽略 RFC 文本封装允许的 SP、HT、VT、FF、CR 和 LF。正文格式失败使用 xrt.pem 的 XPEM_ERROR_BODY,并通过 xrtErrorCause 保留原始 xrt.codec 错误。

规范编码

编码器统一输出五连字符边界、每行恰好最多 64 个 Base64 字符和 LF 换行。xrtPemEncode 在输出为空且容量为零时只查询长度;实际写入要求容量额外包含末尾零字节。容量和重叠失败不会修改输出缓冲。

空二进制正文生成相邻的开始行与结束行,不添加无意义空行。

示例与测试

  • examples/asn1/pem/main.c
  • tests/asn1/test_pem.c
  • tests/asn1/test_pem_oom.c
  • tests/single/test_single_pem.c

测试覆盖多块与说明文本、三种换行、非零结尾输入、空正文、64 字符换行、错误原因链、非法边界、容量与重叠原子性、OOM 和单头生成。

xpemblock类型

PEM 块的标签、正文和完整消费区间都借用原始输入;Raw 包含存在的结束行换行。

字段类型语义
Labelxstrview标签
Bodyxstrview主体
RawxstrviewRaw
typedef struct xpemblock { xstrview Label; xstrview Body; xstrview Raw; } xpemblock;

xpemcursor类型

PEM 游标允许输入前后存在说明文本,并按出现顺序遍历多个块。

字段类型语义
Textxstrview文本视图
Offsetsize_t偏移量
typedef struct xpemcursor { xstrview Text; size_t Offset; } xpemcursor;

xpemresult类型

PEM 读取结果把正常结束与格式错误分开。

值语义
XPEM_ERROR失败
XPEM_DONE完成
XPEM_BLOCK已读取一个块
typedef enum xpemresult { XPEM_ERROR = -1, XPEM_DONE = 0, XPEM_BLOCK = 1 } xpemresult;

xpemerror类型

PEM 模块稳定错误码。

值语义
XPEM_ERROR_BOUNDARY失败
XPEM_ERROR_LABEL失败
XPEM_ERROR_BODY失败
XPEM_ERROR_NOT_FOUND无匹配块
typedef enum xpemerror { XPEM_ERROR_BOUNDARY = 1, XPEM_ERROR_LABEL, XPEM_ERROR_BODY, XPEM_ERROR_NOT_FOUND } xpemerror;
借用式遍历

xrtPemInit函数

初始化一个严格有界、借用输入的 PEM 游标。

bool xrtPemInit(xpemcursor* pCursor, cstr sText, size_t iSize)

参数

参数方向约束说明
pCursor输出非空接收游标
sText输入借用PEM 文本
iSize输入—文本字节数
返回值
返回含义失败时状态
true已初始化—
false参数非法XERR_ARGUMENT
错误
  • XERR_ARGUMENT — 指针为空或参数非法

范例已编译验证

pem_tour · 初始化游标

	if ( !xrtPemInit(&Cursor, sText, sizeof(sText) - 1u) ) {

xrtPemRead函数

读取下一个 PEM 块;失败时游标和输出保持不变。

xpemresult xrtPemRead(xpemcursor* pCursor, xpemblock* pBlock)

参数

参数方向约束说明
pCursor输入/输出已初始化目标游标
pBlock输出非空接收块视图
返回值
返回含义失败时状态
XPEM_BLOCK已读取一个块,pBlock 有效—
XPEM_DONE输入耗尽,无更多块不设错误
XPEM_ERROR参数或格式错误XERR_ARGUMENT / xrt.pem 域错误
错误
  • XERR_ARGUMENT — 指针为空或游标状态非法
  • xrt.pem / XPEM_ERROR_LABEL(XERR_PROTOCOL) — 标签缺失或不完整
  • xrt.pem / XPEM_ERROR_BOUNDARY(XERR_PROTOCOL) — 起止边界不匹配或残缺

范例已编译验证

pem_tour · 读取下一块

	while ( xrtPemRead(&Cursor, &Block) == XPEM_BLOCK ) {
查找与解码

xrtPemFind函数

查找第一个标签完全匹配的 PEM 块。

bool xrtPemFind(cstr sText, size_t iSize, cstr sLabel, xpemblock* pBlock)

参数

参数方向约束说明
sText输入借用PEM 文本
iSize输入—文本字节数
sLabel输入非空、零结尾目标标签
pBlock输出非空接收块视图
返回值
返回含义失败时状态
true已找到,pBlock 有效—
false未找到或失败XERR_NOT_FOUND 等
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • xrt.pem / XPEM_ERROR_LABEL(XERR_VALUE) — 标签为空或非法
  • xrt.pem / XPEM_ERROR_NOT_FOUND(XERR_NOT_FOUND) — 无匹配块
  • xrt.pem 域错误 — 遍历中遇到的格式错误(LABEL/BOUNDARY)

范例已编译验证

pem · 查找块

		!xrtPemFind(sText, strlen(sText), "XRT DATA", &Block) ) {

xrtPemDecode函数

解码 PEM 块正文;输出为空且容量为零时只验证并查询长度。

bool xrtPemDecode(const xpemblock* pBlock, void* pOutput, size_t iCapacity, size_t* pOutputSize)

参数

参数方向约束说明
pBlock输入非空、来自 Init/Read/FindPEM 块
pOutput输出允许空空 + 零容量 = 验证查询
iCapacity输入—输出容量
pOutputSize输出非空接收解码字节数
返回值
返回含义失败时状态
true已解码(或已验证)—
false正文或容量非法xrt.pem / 底座错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • xrt.pem / XPEM_ERROR_BODY(XERR_PROTOCOL) — 正文不是规范 Base64
  • XERR_RANGE — 容量不足,由 Base64 底座透传,不写半个结果

范例已编译验证

pem_tour · 解码正文

		if ( xrtPemDecode(&Block, Data, sizeof(Data), &iDataSize) ) {

xrtPemDecodeNew函数

解码并返回由 xrtFree 释放的字节。

bytes xrtPemDecodeNew(const xpemblock* pBlock, size_t* pOutputSize)

参数

参数方向约束说明
pBlock输入非空、来自 Init/Read/FindPEM 块
pOutputSize输出非空接收解码字节数
返回值
返回含义失败时状态
非空解码字节,xrtFree 释放—
NULL失败xrt.pem 域错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • xrt.pem / XPEM_ERROR_BODY(XERR_PROTOCOL) — 正文不是规范 Base64
  • XERR_MEMORY — 分配失败
  • XERR_RANGE — 长度引起尺寸溢出

范例已编译验证

pem · 分配解码

	pDecoded = xrtPemDecodeNew(&Block, &iDecodedSize);
规范编码

xrtPemEncode函数

生成 RFC 7468 文本,Base64 每行 64 字符并统一使用 LF;输出为空且容量为零时只查询文本长度,实际写入要求额外的末尾零字节。

bool xrtPemEncode(cstr sLabel, const void* pData, size_t iSize, char* sOutput, size_t iCapacity, size_t* pOutputSize)

参数

参数方向约束说明
sLabel输入非空、零结尾块标签
pData输入非空原始字节
iSize输入—字节数
sOutput输出允许空空 + 零容量 = 查询长度
iCapacity输入—容量,须含末尾零字节
pOutputSize输出非空接收不含零字节的长度
返回值
返回含义失败时状态
true已写入并补零—
false参数、标签或容量失败XERR_ARGUMENT 等
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • xrt.pem / XPEM_ERROR_LABEL(XERR_VALUE) — 标签为空或非法
  • XERR_RANGE — 容量不足(须含末尾零字节),不写半个结果

范例已编译验证

pem_tour · 规范编码

	if ( !xrtPemEncode("DATA", Data, iDataSize, Text, sizeof(Text),
		&iTextSize) ) {

xrtPemEncodeNew函数

生成并返回由 xrtFree 释放的 PEM 文本。

str xrtPemEncodeNew(cstr sLabel, const void* pData, size_t iSize)

参数

参数方向约束说明
sLabel输入非空、零结尾块标签
pData输入非空原始字节
iSize输入—字节数
返回值
返回含义失败时状态
非空零结尾 PEM 文本,xrtFree 释放—
NULL失败xrt.pem 域错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • xrt.pem / XPEM_ERROR_LABEL(XERR_VALUE) — 标签为空或非法
  • XERR_MEMORY — 分配失败

范例已编译验证

pem · 分配编码

	sText = xrtPemEncodeNew("XRT DATA", Data, sizeof(Data));

常量索引常量

以下常量在本页所属头文件中定义,语义见公共头注释。

常量说明
XPEM_BLOCK见头文件注释
XPEM_DONE见头文件注释
XPEM_ERROR见头文件注释
XPEM_ERROR_BODY见头文件注释
XPEM_ERROR_BOUNDARY见头文件注释
XPEM_ERROR_LABEL见头文件注释
XPEM_ERROR_NOT_FOUND见头文件注释