模块契约TLS

结果与错误

xtlsresult 把正常控制流与错误分开:

  • XTLS_OK:操作完成。
  • XTLS_AGAIN:输入尚不完整,继续喂入数据即可,不设置线程错误。
  • XTLS_CLOSED:收到或完成协议级干净关闭。
  • XTLS_ERROR:协议或调用失败,使用 xrtGetError() 读取 xrt.tls 结构化错误。

xtlserror 进一步区分版本、记录类型、记录长度、Alert、状态、密码、证书、校验和恢复阶段。调用方不需要解析错误字符串。

所有权与线程

  • 基础记录和 Alert API 不分配内存。
  • 所有输入和解析结果都是借用视图。
  • 纯编解码函数没有共享可变状态,可以并发调用。
  • 当前线程已有错误不会因 XTLS_AGAIN 被清除或覆盖。

裁剪与依赖

启用宏:

直接依赖核心模块中的结构化错误体系;核心模块不可裁剪,因此不需要额外启用宏。

记录保护层继续细分:

宏功能直接依赖
XRT_FEATURE_TLS_SESSION无 socket 的公共会话生命周期、惰性有界队列、所有权输入与 Span 输出tls_context、tls_record、tls_messages、tls_schedule、net_buffer、crypto_core
XRT_FEATURE_TLS_CLIENT独立客户端配置、真实后端能力过滤和初始 ClientHellotls_session、tls_hello_write、tls_handshake_reader、tls_schedule、tls_key_exchange、random_secure
XRT_FEATURE_TLS_SERVER独立服务端配置、SNI/ALPN、身份选择、TLS 1.3 证书航班和 READY 状态tls_session、tls_hello_write、tls_messages_write、tls_schedule、tls_key_exchange、tls_identity、random_secure
XRT_FEATURE_TLS_VERIFY不可变信任快照、默认对端验证、自定义信任决策和 TLS 1.3 握手验签tls_negotiate、x509_store、x509_identity、x509_verify、crypto_core
XRT_FEATURE_TLS_CLIENT_VERIFY客户端 Certificate 与 CertificateVerify 状态tls_client、tls_verify 及所选 X.509 密码后端
XRT_FEATURE_TLS_RESUME不可变 TLS 1.3 ticket/PSK 恢复资产、有效期与票据年龄tls、time、crypto_core
XRT_FEATURE_TLS_CLIENT_RESUME客户端恢复主密钥、票据 PSK 派生、ClientHello binder 和有界显式交接tls_client_verify、tls_resume、tls_psk_write、crypto_sha256
XRT_FEATURE_TLS_SERVER_RESUME服务端外部票据查找、严格 binder 校验、票据签发和恢复对象交接tls_server、tls_resume、tls_psk、tls_psk_write
XRT_FEATURE_TLS_STREAMTCP 与 TLS 会话组合流、双层背压、阶段超时和认证关闭net_tcp、tls_client、tls_server
XRT_FEATURE_TLS_STREAM_FUTURETLS Stream 的发送、接收和条件等待 Futuretls_stream、future
XRT_FEATURE_TLS_STREAM_DIAL主机解析、TCP 地址竞速和 TLS 握手组成的受管客户端拨号tls_stream、net_tcp_dial
XRT_FEATURE_TLS_STREAM_DIAL_FUTURE受管 TLS Dial 的 Future 结果与协作取消tls_stream_dial、future
XRT_FEATURE_TLS_IDENTITY不可变证书链与外部签名器核心tls_negotiate、x509_parse、crypto_core
XRT_FEATURE_TLS_RECORD单向密钥、序列号、nonce、TLS 1.2/1.3 记录保护骨架tls、crypto_core
XRT_FEATURE_TLS_RECORD_AESAES-128-GCM、AES-256-GCM 记录后端tls_record、crypto_aes_gcm
XRT_FEATURE_TLS_RECORD_CHACHAChaCha20-Poly1305 记录后端tls_record、crypto_chacha20_poly1305
XRT_FEATURE_TLS_HANDSHAKE握手消息与扩展 framingtls
XRT_FEATURE_TLS_HANDSHAKE_READER跨记录握手消息的有界自适应重组tls_handshake
XRT_FEATURE_TLS_HELLO扩展游标、SNI、ALPN、版本、组、签名、key_share 与 Hello 严格解析tls_handshake
XRT_FEATURE_TLS_NEGOTIATE无状态版本、套件、签名与 key_share 选择tls_hello
XRT_FEATURE_TLS_POLICY客户端与服务端共享的有序协议偏好及完整校验tls_negotiate、tls_key_exchange
XRT_FEATURE_TLS_CONTEXT深拷贝策略、硬限制与公平性预算的共享只读快照tls_policy
XRT_FEATURE_TLS_KEY_EXCHANGE命名组元数据、后端探测与调用方缓冲密钥交换骨架tls
XRT_FEATURE_TLS_KEY_EXCHANGE_X25519X25519 临时密钥与共享秘密后端tls_key_exchange、crypto_x25519_keypair
XRT_FEATURE_TLS_KEY_EXCHANGE_X448X448 临时密钥与共享秘密后端tls_key_exchange、crypto_x448_keypair
XRT_FEATURE_TLS_KEY_EXCHANGE_P256secp256r1 临时密钥与共享秘密后端tls_key_exchange、crypto_p256_keypair
XRT_FEATURE_TLS_KEY_EXCHANGE_P384secp384r1 临时密钥与共享秘密后端tls_key_exchange、crypto_p384_keypair
XRT_FEATURE_TLS_HELLO_WRITE增量扩展 writer 与 ClientHello/ServerHello 正文编码tls_hello
XRT_FEATURE_TLS_MESSAGESCertificate、EncryptedExtensions、CertificateVerify、Finished、KeyUpdate 与票据严格解析tls_hello
XRT_FEATURE_TLS_MESSAGES_WRITE握手语义消息的调用方缓冲编码tls_messages
XRT_FEATURE_TLS_AUTH_MESSAGESCertificateRequest、TLS 1.2 ECDHE、OCSP 状态与压缩证书 framingtls_messages
XRT_FEATURE_TLS_AUTH_MESSAGES_WRITE认证消息与证书颁发者向量的调用方缓冲编码tls_auth_messages
XRT_FEATURE_TLS_SCHEDULEtranscript、TLS 1.2 PRF、TLS 1.3 密钥调度骨架tls、crypto_core
XRT_FEATURE_TLS_SCHEDULE_SHA256SHA-256 transcript、HMAC 与 HKDF 后端tls_schedule、crypto_hkdf_sha256
XRT_FEATURE_TLS_SCHEDULE_SHA384SHA-384 transcript、HMAC 与 HKDF 后端tls_schedule、crypto_hkdf_sha512

只需要通用容器、文件或明文网络时可以完全裁掉 TLS。会话、握手、恢复、TCP 适配和主机名拨号都使用独立细粒度宏,不会被基础记录解析强制带入。

协议范围

XRT 只协商 TLS 1.2 和 TLS 1.3,默认优先 TLS 1.3,不提供 SSLv2、SSLv3、TLS 1.0 或 TLS 1.1 兼容路径。记录头中的 legacy_version 是线路兼容字段,不等于最终协商版本。

当前密码范围只保留 AEAD 与前向保密路径:TLS 1.3 支持 AES-128-GCM、AES-256-GCM 和 ChaCha20-Poly1305;TLS 1.2 支持 ECDHE-ECDSA/ECDHE-RSA 配合上述 AEAD。静态 RSA、CBC、RC4、3DES 和 TLS 压缩不进入新体系。xrtTlsCipherName() 返回可用于日志的标准套件名。

xrtTlsCipherInfo() 返回进程期只读的 xtlscipherinfo,把每个套件唯一的版本、摘要、AEAD、TLS 1.2 认证类型、密钥、静态 IV、显式 nonce 和认证标签长度集中表达。未知套件返回 NULL 且不设置错误,适合策略探测;调用方不能释放或修改返回对象。TLS 1.3 的 Authentication 固定为 XTLS_CIPHER_AUTH_INDEPENDENT,因为证书或 PSK 认证不再由密码套件决定。

xtlshash 和 xtlsaead 描述协议选择,不要求启用具体密码后端;xtlscipherauth 只表达 TLS 1.2 套件认证约束。记录保护、密钥调度和协商层共享这份元数据,避免各自维护一套容易漂移的套件表。

基础限制:

常量含义
XTLS_RECORD_HEADER_SIZE固定 5 字节记录头
XTLS_RECORD_PLAINTEXT_MAX16384 字节明文上限
XTLS12_RECORD_CIPHERTEXT_MAX18432 字节 TLS 1.2 密文上限
XTLS13_RECORD_CIPHERTEXT_MAX16640 字节 TLS 1.3 密文上限
XTLS13_INNER_PLAINTEXT_MAX16385 字节内容、类型与零填充总上限
XTLS_AES_GCM_RECORD_LIMIT单组 AES-GCM 流量密钥最多处理 2^24 条记录

记录解析

解析器适合任意分片方式:

  • 输入不足 5 字节时,返回 XTLS_AGAIN,Required 为 5。
  • 已有完整头但负载不足时,返回 XTLS_AGAIN,Required 为整条记录长度。
  • 只有 XTLS_OK 会写入 Record。
  • Record.Payload 借用输入内存,不分配、不复制。
  • 输入中包含多条记录时只返回第一条,EncodedSize 给出应消费的字节数。

未知内容类型、非法兼容版本和超过 TLS 1.2 密文硬上限的长度会立即返回协议错误,不会等待攻击者声明的超大负载。

记录编码

编码器允许输入与输出重叠,适合在已有明文前原地腾出记录头。容量不足时输出保持不变。xrtTlsRecordSize() 可提前计算总长度。

记录编码器是协议工具,不会绕过后续会话层的加密、序列号和状态检查;应用数据应优先通过 TLS 会话写入。

握手与扩展 Framing

tls_handshake 公开握手消息和单个扩展的零分配线路原语。它只处理 framing,不在这一层判断某种消息或扩展能否出现在当前握手状态:

  • 握手头固定 4 字节,正文长度是 24 位;扩展头固定 4 字节,负载长度是 16 位。
  • 输入不足返回 XTLS_AGAIN 和继续解析所需的精确总长度,不设置错误,也不修改输出。
  • 成功结果借用输入,只消费开头第一条消息或第一个扩展,便于上层连续处理聚合记录。
  • 未知握手类型和未知扩展类型会保留线路数值。后续状态机负责按版本、角色和阶段决定忽略、拒绝或交给扩展处理器。
  • 编码允许正文或负载与输出精确重叠,容量不足和不可编码长度不会修改输出。
  • xrtTlsHandshakeSize() 与 xrtTlsExtensionSize() 在分配或预留缓冲前执行 24 位、16 位线路上限检查。

通用 framing 不分配声明长度对应的内存。会话层必须另外配置实际握手消息上限,避免对端用接近 16 MiB 的合法 24 位长度占用过多连接内存。ClientHello、ServerHello、SNI、ALPN、supported_versions、key_share 等语义解析建立在这些原语之上,并负责重复扩展和上下文约束。

握手消息 Reader

tls_handshake_reader 解决握手消息跨 TLS record、跨 socket 读取分片的问题,同时不把每个连接变成固定大缓冲:

  • 完整消息已经位于单个 Input 时直接返回借用输入的视图,不分配也不复制。
  • 1 到 3 字节的分片头只写入 reader 内联的 4 字节 Header;确认正文需要跨输入后才分配重组区。
  • 重组区按实际收到的字节以约 1.5 倍增长,不会在看到 24 位声明长度时立即分配整条消息。
  • 默认完整编码消息上限是 1 MiB,可通过 xtlshandshakereaderconfig.Limit 在 4 字节到线路最大值之间调整。超限头零消费、零分配并保持 reader 不变。
  • 默认只跨消息保留不超过 16 KiB 的容量;证书链等大消息在下一次 Read() 或 Reset() 时释放,不会永久放大每连接常驻内存。
  • 一次只发布一条消息,Consumed 不跨过下一条聚合消息。跨分片结果借用 reader,生命周期到下一次 Read()、Reset() 或 Unit();单片结果借用本次 Input。
  • reader 自己的可移动分配区不能作为下一段 Input,API 会在 realloc 前显式拒绝别名。

xrtTlsHandshakeReaderRequired() 返回当前完整消息长度;只有部分头或空闲时返回 4。正常分片返回 XTLS_AGAIN 且不设置错误,容量不足、配置错误和超限消息通过结构化 TLS 错误表达。

Hello 与核心扩展

tls_hello 在 framing 之上提供三层能力:

1. xtlsextensioncursor、xrtTlsExtensionsRead() 和 xrtTlsExtensionsFind() 遍历完整扩展向量,保留未知类型,并严格拒绝任何重复扩展。 2. xrtTlsServerNames()、xrtTlsProtocols()、xrtTlsClientVersions()、xrtTlsGroups()、xrtTlsSignatures() 和 key_share API 解析独立扩展数据。游标和结果都借用输入,不把 SNI、ALPN 或公钥复制到固定数组。 3. xrtTlsClientHelloParse() 与 xrtTlsServerHelloParse() 一次验证完整正文并发布易用视图。声明长度必须恰好消费正文,已知扩展采用精确长度规则,HelloRetryRequest 会由固定 random 自动识别。

核心语义边界:

  • 扩展、SNI 名称类型、supported_versions、supported_groups、signature_algorithms 和客户端 key_share 都检查重复项。
  • ALPN ProtocolName 是非空不透明字节,不强加 ASCII、逗号分隔或末尾零字符限制;服务端选择必须恰好包含一个协议。
  • xrtTlsProtocolFind() 区分找到、未找到和畸形列表;xrtTlsProtocolSelect() 按服务端偏好列表顺序选择双方第一个交集,不内置 HTTP 协议优先级。
  • ClientHello 密码套件向量必须非空且为偶数字节,压缩列表必须包含 null compression;只要声明支持 TLS 1.3,兼容压缩字段就必须严格为单个零。
  • TLS 1.3 ClientHello 的 pre_shared_key 必须是最后一个扩展。
  • ServerHello key_share 必须恰好包含一个非空密钥;HelloRetryRequest key_share 必须恰好包含一个两字节命名组,不能用宽松的“大于等于长度”规则接受尾随数据。
  • xrtTlsRetryCookie() 与 xrtTlsWriterRetryCookie() 公开 HRR/第二个 ClientHello 共用的 16 位非空 Cookie 向量;解析结果借用输入,长度声明必须精确消费扩展数据。
  • Hello 解析器只负责线路结构和已知核心扩展的局部语义。密码套件选择、版本协商、扩展出现位置、SNI 证书选择和 ALPN 策略属于后续状态机。

扩展唯一性检测不分配 8K 类型位图。游标只保存 32 字节桶状态;发生桶碰撞时最多回看同桶的 16 位类型,因此正常路径为线性扫描,最坏回看次数也受 16 位类型空间约束。

无状态协商

tls_negotiate 只选择协议参数,不生成随机数、密钥或签名,也不修改会话状态。所有偏好数组都由调用方提供并按本地顺序解释,因此服务端、客户端、硬件能力探测和应用策略可以共享同一套选择器,不受库内硬编码优先级限制。

API 分层如下:

  • xrtTlsIdsSelect() 按本地偏好求任意 16 位标识列表的交集,未知和私有线路值不会被截断。xrtTlsKeyShareFind() 在完整客户端 key_share 负载中查找指定组。
  • xrtTlsVersionSelect() 选择已解析的 supported_versions;xrtTlsClientVersionSelect() 进一步处理扩展缺失语义,此时只能选择 TLS 1.2,绝不会根据 legacy_version 猜测 TLS 1.3。
  • xrtTlsCipherSelect() 使用 xrtTlsCipherInfo() 和 xtlsidentitytype 过滤版本与 TLS 1.2 认证约束。TLS 1.3 套件与证书或 PSK 身份独立;TLS 1.2 ECDHE_RSA 只接受 RSA 身份,ECDHE_ECDSA 按 RFC 8422 接受 ECDSA 或 EdDSA 身份。
  • xrtTlsSignatureInfo() 返回方案要求的身份类别、摘要长度和协议版本范围。元数据是进程期只读对象,未知线路值返回 NULL 且不设置错误;会话策略、身份选择和签名实现不再分别维护方案表。
  • xrtTlsSignatureSelect() 区分版本规则。TLS 1.3 RSA 握手签名只选择 PSS,ECDSA 方案必须匹配身份曲线;TLS 1.2 的 ECDSA 线路值仍是哈希/签名对,不把名称中的曲线错误地强加到证书密钥。
  • xrtTlsKeyShareSelect() 先验证 key_share 是 supported_groups 的同序子序列。XTLS_KEY_SHARE_PREFER_GROUP 严格保留本地组优先级,首选组没有 share 时发布 Retry=true;XTLS_KEY_SHARE_PREFER_READY 优先选择已经携带 share 的共同组以避免额外往返,没有可用 share 时才请求首选共同组。

所有选择器使用 XTLS_ITEM_VALUE、XTLS_ITEM_DONE 和 XTLS_ITEM_ERROR:无交集是正常 DONE,不设置错误且不修改输出;畸形对端输入或非法本地配置返回结构化 xrt.tls 协商错误。结果中的 key-share 公钥借用 ClientHello 输入,生命周期不超过原输入。

xtlsidentitytype 只描述握手签名公钥类别,不等同于后续持有证书链、私钥和策略的身份对象。这一层故意不判断密码后端是否编入、RSA-PSS 密钥参数和模数长度、证书链是否满足对端 signature_algorithms_cert、组公钥是否在曲线上,也不决定 PSK、SNI 或 ALPN。会话配置根据实际启用的密码后端和密钥能力构造偏好数组,身份选择发生在 SNI 之后,密钥交换层再验证组专用公钥并计算共享秘密;自定义组或签名仍可使用通用标识 API 和原始扩展视图实现。

TLS 策略

tls_policy 把原先散落在客户端、服务端和身份分支中的硬编码优先级收敛成一份借用式配置。默认初始化不分配内存,数组指向进程期只读常量;调用方可以逐项替换为生命周期覆盖上下文创建过程的自有数组:

策略校验不修改输入且不分配内存。版本和套件必须非空;所有列表都必须保持指针/数量一致、元素唯一且属于内建会话能力。每个套件必须对应一个启用版本,每个启用版本也必须至少保留一个套件。组和签名列表可以为空,为恢复会话、PSK 或后续外部认证路径保留扩展空间;非空签名必须能用于至少一个启用版本,因此 TLS 1.2 专用 PKCS#1 方案不会混入纯 TLS 1.3 策略。

策略只描述协议偏好,不承诺当前裁剪构建已经包含对应密码执行后端。后续客户端或服务端会话创建会结合 xrtTlsGroupAvailable()、记录 AEAD、身份私钥和验证后端生成实际执行路径;原始策略快照保持不变,便于同一配置服务不同机器和硬件能力。

共享上下文

tls_context 把调用方策略转换为可跨线程共享的只读快照。上下文使用一次紧凑分配保存对象和四个有序数组;创建返回后不再借用 xtlspolicy、数组或 xtlscontextconfig。连接和会话只保留上下文引用,证书或 ticket key 轮换通过创建新上下文并替换上层引用完成,不在活动对象中原地修改策略。

xtlslimits 不会触发预分配。FeedLimit、SendLimit 和 PlainLimit 只是后续自适应 xnetbuf 队列的硬上限;空闲 context 或 session 不常驻 8 KiB 缓冲。三个队列至少容纳一条最大合法记录,避免对端发送标准大小记录后永久停滞。HandshakeLimit 限制单条重组消息,默认 1 MiB;RecordBudget 和 HandshakeBudget 限制一次非阻塞驱动处理的工作量,防止高吞吐连接独占 Worker。

xrtTlsListenerConfigInit 默认限制 128 条并发握手和 1024 条已完成待领取连接。握手预算在创建 TLS 会话之前执行,调用方仍可显式设置 HandshakeLimit 和 AcceptQueueLimit。本次只收紧现有默认值,没有新增资源 profile 或改变缓冲的惰性分配策略。

上下文当前保存协议策略,不把 Worker 专属 xnetbufpool 放入共享对象,也不把“协议已知”误当成“当前构建后端可执行”。客户端或服务端会话创建时再将上下文策略与已编入的组、AEAD、身份和验证能力求交集;无法得到完整执行路径时创建失败,而不会静默改写调用方优先级。

命名组与密钥交换

tls_key_exchange 把协议组信息与密码后端分开。xrtTlsGroupInfo() 对 X25519、X448、secp256r1 和 secp384r1 始终返回进程期只读元数据;xrtTlsGroupAvailable() 才反映当前裁剪配置。未知组查询返回 NULL 或 false 且不设置错误,便于策略层探测;对未知组或已裁剪后端执行生成、派生时返回 XTLS_ERROR_KEY_EXCHANGE。

  • PrivateSize、PublicSize 和 SharedSize 是精确有效长度;输出容量可以更大,函数只写对应有效区间。
  • xrtTlsKeyShareGenerate() 要求私钥与公钥有效输出区间不重叠。它先验证组、指针、容量和重叠,再调用随机源;任何失败都不修改两段输出。
  • xrtTlsKeyShareDerive() 要求本地私钥和对端公钥视图恰好等于元数据长度。输出可以覆盖本地私钥或对端公钥,因为四个后端都在发布结果前完成输入消费;容量不足或密码校验失败时输出不变。
  • X25519 与 X448 在派生阶段以常量时间聚合结果并拒绝全零共享秘密;P-256 与 P-384 验证私钥范围、未压缩点格式、坐标范围和曲线成员关系。
  • 密码后端错误作为 xrt.tls 密钥交换错误的 Cause 保留,因此上层宿主和 C 调用方既能按 TLS 阶段处理,也能继续读取具体 xrt.crypto 原因。
  • API 不分配堆内存,也不保存每连接状态。会话层只需在组选择完成后按元数据申请一组精确缓冲,不再为每个对象常驻四套私钥、公钥或最大共享秘密。

这层只执行传统 ECDHE/XDH。未来新增命名组时,协议解析和协商仍可通过 uint16 保留未知线路值;增加密码实现只需新增独立后端宏,不需要修改 Hello parser 的通用逃生路径。

Hello Writer

tls_hello_write 把“高级构建器”和“直接线路控制”放在同一条分层路径上。xtlswriter 只引用调用方缓冲,不分配、不扩容,也不包含固定 1KB 或 8KB 数组:

常见路径可直接使用 xrtTlsWriterHostName()、xrtTlsWriterProtocols()、xrtTlsWriterIds()、版本和 key_share helper。少见或私有扩展使用 xrtTlsWriterExtension() 追加原始负载,不必修改协议层。每次追加先验证当前向量、重复类型、线路上限、容量和输入重叠;只有完整扩展写入成功才推进 Writer.Size。

xrtTlsClientHelloSize() / xrtTlsServerHelloSize() 先验证视图和核心扩展语义,再返回精确正文长度;对应 Encode() 在容量不足、Retry random 与标记不一致、TLS 1.3 压缩字段错误或输入输出重叠时保持输出不变。正文写好后可直接交给 xrtTlsHandshakeEncode() framing,或由后续会话层写入 transcript 和记录层。

重叠规则是显式的:原始 xrtTlsWriterExtension() 使用 memmove,允许负载来自 writer 缓冲;SNI、ALPN、标识数组、key_share 和完整 Hello 编码拒绝输入与本次目标区域重叠,避免多字段写入覆盖尚未读取的数据。空扩展向量编码时省略可选的两字节扩展总长字段。

大 ClientHello 只受调用方容量和 TLS 线路上限约束。回归包含超过旧版 1024 字节栈缓冲的 255 项 ALPN 列表,并由严格解析器完成 round-trip 验证。

握手语义消息

tls_messages 把握手 framing 与会话状态机之间反复出现的字段切分收敛为一层公共协议 API。它不解析 X.509、不执行签名,也不决定当前状态是否允许某种消息;它只保证正文符合 TLS 1.2 或 TLS 1.3 的精确线路结构。

证书链采用消息视图与游标两级 API:

  • TLS 1.2 与 TLS 1.3 的请求上下文、列表和条目格式分别解析,不使用“至少还有这些字节”的宽松规则接受尾随数据。
  • 证书列表可以为空,由握手角色和认证策略决定该场景是否合法;非空条目的 DER 数据必须至少一字节。
  • 游标不保存固定证书指针数组,因此没有 4、8、16 项链上限。单张证书使用 24 位长度,70KB 以上证书不会被错误压缩成 16 位。
  • TLS 1.3 每个证书条目的扩展向量都会验证 framing 与类型唯一性,并保留未知扩展供状态机、OCSP 和 SCT 层处理。
  • 所有视图借用握手正文;解析和遍历不分配、不复制证书。

其他消息使用短路径 API:

API契约
xrtTlsEncryptedExtensionsParse()16 位扩展总长必须精确,拒绝重复扩展,并校验 ALPN 单选、空确认扩展和常用限制字段
xrtTlsCertificateVerifyParse()签名方案保留未知线路值,签名非空且 16 位长度必须恰好消费正文
xrtTlsFinishedParse()调用方传入协商后的验证数据长度;TLS 1.2 通常为 12,TLS 1.3 为当前 transcript 摘要长度
xrtTlsKeyUpdateParse()正文必须恰好一字节且只能是 not_requested 或 requested
xrtTlsSessionTicketParse()区分 TLS 1.2 与 TLS 1.3 格式;TLS 1.3 票据必须非空、寿命不超过七天,early_data 扩展必须恰好四字节

未知扩展继续保留,协议状态机再验证它是否曾由对端提供、能否出现在当前消息和是否受本地策略允许。这样协议工具既不会提前封死未来扩展,也不会把重复类型、畸形长度或已知字段的局部错误拖到密码阶段。

语义消息编码

tls_messages_write 为上述消息提供精确 Size() 与失败原子的 Encode():

  • Certificate 与 NewSessionTicket 先验证全部字段、扩展、线路上限、容量和输入重叠,再写第一个字节;失败时输出不变。
  • Certificate 接受调用方证书条目数组,不分配完整消息,不只发送叶证书,也不内置固定链数量。
  • EncryptedExtensions、CertificateVerify 与 Finished 的单一负载使用 memmove,允许原位向前或向后腾出前缀。
  • 多字段消息拒绝字段与目标区域重叠;描述结构本身在写入前做局部快照,避免前缀覆盖后续元数据。
  • Size() 同时充当可编码性验证,不会静默丢弃 TLS 1.2 不存在的请求上下文、条目扩展、nonce 或 age_add。

语义编码只产生握手正文。调用方可以继续交给 xrtTlsHandshakeEncode() 生成四字节握手头;会话层则会在同一路径中追加 transcript 并交给记录保护层。原始扩展向量始终是逃生口,高级扩展 writer 只是可选构建器。

认证消息

tls_auth_messages 继续拆出认证状态机需要、但不应在客户端和服务端反复手写的线路对象:

对象公开契约
CertificateRequest分别解析 TLS 1.2 与 TLS 1.3 格式;证书类型、签名方案、证书签名方案、请求上下文和颁发者名称都保留为借用视图
TLS 1.2 ECDHE ServerKeyExchange发布命名组、公钥、完整 ServerECDHParams 和签名;Parameters 可直接拼接两个 random 后参与验签
TLS 1.2 ECDHE ClientKeyExchange严格解析单个一字节长度 ECPoint
CertificateStatus当前只接受具有已知正文形状的 OCSP 类型,并发布不透明响应
CompressedCertificate发布算法、声明的解压长度和压缩负载,不在 framing 层绑定 zlib、Brotli 或 Zstandard 后端

CertificateRequest 的颁发者名称使用公共两级 API:xrtTlsAuthorities() 验证完整 16 位总长,xrtTlsAuthoritiesRead() 零拷贝遍历任意数量的非空 DER DistinguishedName。TLS 1.2 允许空列表;TLS 1.3 一旦携带 certificate_authorities 扩展就必须至少有一个名称。TLS 1.3 的 signature_algorithms 是必选扩展,signature_algorithms_cert 缺失时由后续状态机按协议回退,不在解析结果中伪造一份列表。

语法层不根据当前内建密码后端限制 Group、签名方案或压缩算法。未知线路值继续发布,协商状态机再检查它是否由本端提供、是否与证书和密码套件匹配,以及公钥长度是否符合所选组。这样新增命名组或外置压缩后端不需要修改基础 parser。OCSP CertificateStatus 是例外:未知状态类型没有标准化的可跳过正文形状,因此 parser 明确拒绝。

压缩证书 parser 只接受非空负载和 24 位非零声明长度。后续解压层必须在分配前检查每连接配置上限,要求实际输出恰好等于 UncompressedSize,再把输出交给普通 xrtTlsCertificateParse();不能信任对端声明直接分配接近 16 MiB 的缓冲。

tls_auth_messages_write 为每个对象提供精确 Size() 与失败原子的 Encode()。CertificateRequest 和 ServerKeyExchange 是多字段消息,字段不得与目标写入区重叠;ClientKeyExchange、CertificateStatus 和 CompressedCertificate 只有一个不透明负载,使用 memmove 支持原位腾出线路前缀。xrtTlsAuthoritiesSize() / Encode() 接受名称视图数组,不限制条目数量,只受 16 位向量总长限制。

认证消息 API 不执行签名、验签、密钥交换、OCSP ASN.1 解析或解压。这些能力由会话、X.509、crypto 和可选压缩后端逐层组合,避免协议 framing 与算法实现形成不可裁剪的强耦合。

记录保护内部契约

记录保护层是握手与会话的内部底座,不额外公开一套让应用直接管理流量密钥的 API。它具备以下已压实边界:

  • 每个 record-key 只管理一个发送或接收方向,序列号不会跨方向共享。
  • TLS 1.3 使用静态 IV 异或 64 位序列号,外层类型固定为 application_data,打开后去除内层类型与零填充。
  • TLS 1.2 AES-GCM 使用 4 字节静态盐和 8 字节显式序列号;ChaCha20-Poly1305 不在线路上发送显式 nonce。
  • 只有认证成功才递增接收序列号;认证失败不会写出明文,也不会改变类型结果。
  • TLS 1.3 角色会话写入应用数据时,在 AES-GCM 的 2^24 - 1(ChaCha20-Poly1305 的 UINT64_MAX - 1)计数处先发送 KeyUpdate,再用新密钥发送数据,为旧密钥保留最后一个记录号。输出背压可以重试,不会重复提交 epoch。控制消息密集但不写应用数据的连接仍可主动 KeyUpdate;TLS 1.2 和底层记录接口在硬上限处拒绝继续使用密钥,由应用关闭或重建连接。
  • TLS 1.3 单条记录可选零填充;TLS 1.2 AEAD 记录不接受这一参数。
  • 明文与密文缓冲允许精确原位处理;TLS 1.2 AES 打开时明文起点是显式 nonce 后的密文起点。

记录后端不分配堆内存。会话层后续使用网络自适应缓冲承接密文和明文,不为每个连接预留固定 8K 记录数组。

Transcript 与密钥调度

调度层是握手状态机的内部密码底座,不公开应用直接传入 traffic secret 的 API。它把摘要算法选择与握手解析分开,并保证以下契约:

  • 一个 xtlstranscript 只保存协商密码套件实际使用的 SHA-256 或 SHA-384 状态,不同时计算两种摘要。
  • transcript 支持任意分块追加和不结束状态的摘要快照;失败不会把未初始化或错误长度结果当成有效摘要。
  • HelloRetryRequest 使用 RFC 8446 规定的 synthetic message_hash 重建 transcript,替换过程成功前不修改原状态。
  • TLS 1.3 提供 HKDF-Extract、HKDF-Expand-Label、Derive-Secret 和 Finished 内部原语;secret、transcript hash 与输出长度都按所选摘要严格检查。
  • HkdfLabel 支持协议允许的 249 字节调用方 label 和 255 字节 context,完整编码最大 514 字节,不沿用旧实现的 256 字节固定缓冲限制。
  • TLS 1.2 PRF 流式处理 label || seed,不复制到固定数组;大于旧版 256 字节限制的 seed 仍可正常派生。
  • SHA-256 和 SHA-384 是独立裁剪后端。只启用公共调度骨架时,所有算法请求以结构化 unsupported 错误拒绝。
  • 调度原语不分配堆内存;摘要、HMAC 中间状态和临时 secret 在退出前清零。

TLS 1.3 Expand-Label 输出同时受 16 位线路长度和 HKDF 的 255 个摘要块限制。TLS 1.2 PRF 单次内部派生上限为 65535 字节;握手状态机仍应只请求协议实际需要的几十到数百字节。

Alert

xrtTlsAlertParse() 和 xrtTlsAlertEncode() 处理严格的两字节 Alert 负载。xrtTlsAlertName() 返回稳定英文名称,便于日志和结构化诊断。

未知 Alert 描述仍会保留其线路数值,由会话状态机按照协议决定是否终止;非法 Alert 级别和错误长度会被拒绝。

参考标准

范例与测试

  • examples/tls/messages/main.c
  • examples/tls/auth_messages/main.c
  • examples/tls/key_exchange/main.c
  • examples/tls/context/main.c
  • examples/tls/record/main.c
  • tests/tls/test_tls.c
  • tests/tls/test_tls_mutation.c
  • tests/tls/test_tls_record.c
  • tests/tls/test_tls_record_aes.c
  • tests/tls/test_tls_record_chacha.c
  • tests/tls/test_tls_handshake.c
  • tests/tls/test_tls_handshake_mutation.c
  • tests/tls/test_tls_handshake_reader.c
  • tests/tls/test_tls_handshake_reader_limits.c
  • tests/tls/test_tls_hello.c
  • tests/tls/test_tls_hello_negative.c
  • tests/tls/test_tls_hello_mutation.c
  • tests/tls/test_tls_key_exchange.c
  • tests/tls/test_tls_key_exchange_negative.c
  • tests/tls/test_tls_context.c
  • tests/tls/test_tls_context_negative.c
  • tests/tls/test_tls_context_oom.c
  • tests/tls/test_tls_hello_write.c
  • tests/tls/test_tls_hello_write_limits.c
  • tests/tls/test_tls_messages.c
  • tests/tls/test_tls_messages_negative.c
  • tests/tls/test_tls_messages_mutation.c
  • tests/tls/test_tls_messages_write.c
  • tests/tls/test_tls_messages_write_limits.c
  • tests/tls/test_tls_auth_messages.c
  • tests/tls/test_tls_auth_messages_negative.c
  • tests/tls/test_tls_auth_messages_mutation.c
  • tests/tls/test_tls_auth_messages_write.c
  • tests/tls/test_tls_auth_messages_write_limits.c
  • tests/tls/test_tls_schedule.c
  • tests/tls/test_tls_schedule_sha256.c
  • tests/tls/test_tls_schedule_sha384.c
  • tests/single/test_single_tls.c
  • tests/single/test_single_tls_auth_messages.c
  • tests/single/test_single_tls_auth_messages_write.c
  • tests/single/test_single_tls_key_exchange.c
  • tests/single/test_single_tls_context.c
  • tests/tls/test_tls_session.c
  • tests/tls/test_tls_session_limits.c
  • tests/tls/test_tls_session_negative.c
  • tests/tls/test_tls_session_oom.c
  • tests/tls/test_tls_session_record.c
  • tests/tls/test_tls_session_record_oom.c
  • tests/single/test_single_tls_session.c
  • tests/single/test_single_tls_session_record.c

公共会话底座

tls_session 位于纯协议层与客户端/服务端状态机之间,公共声明单独放在 <xrt/tls_session.h>。这个头文件明确组合 <xrt/tls.h> 与 <xrt/net.h>,而纯协议头 <xrt/tls.h> 不反向依赖网络。会话本身不调用 socket;后续客户端、服务端和 TCP 适配器共享同一对象与队列,不再维护第二套 TLS 实现。

会话创建入口分别属于后续 tls_client 和 tls_server。公共底座不发布 bool is_server 式构造器,避免角色配置混用;创建后的 xtlssession 由一个线程或 Worker 独占驱动,并持有一份 xtlscontext 引用。

三个持久队列都是惰性 xnetbuf:

  • Feed 保存尚未处理的输入密文,提供复制、借用、接管、自定义释放和完整 xnetbuf 块链接管五种所有权入口。
  • Send 保存等待底层传输发送的密文,通过 Front / Spans 借用视图并由完成路径精确消费。
  • Plain 保存等待应用读取的明文,既支持 Span 零复制解析,也支持 xrtTlsSessionRead() 复制式便捷读取。

收到完整受保护记录时,会话按该记录的精确长度惰性取得一块临时 Scratch。认证成功后,应用数据块直接移动到 Plain,不再复制;认证失败、OOM 或会话销毁都会先擦除临时明文。Scratch 不是每连接常驻的第四个固定缓冲。

创建空会话不会分配任何队列块。FeedLimit、SendLimit 和 PlainLimit 是追加前检查的硬上限;达到上限返回 XTLS_AGAIN,队列、引用所有权、记录序号和当前线程错误都保持不变。xrtTlsSessionFeedBuffer() 在通过上限检查后零复制移动源 xnetbuf 的全部块,成功后源缓冲为空并可复用;XTLS_AGAIN、活动写预留或其他失败都保持源缓冲不变。会话与缓冲必须由同一线程或 Worker 驱动,借用块的原始存活约束不会因移动而改变。所有 Consume API 拒绝超过待处理字节数的完成通知,不会用静默截断掩盖适配器错误。会话销毁时,其拥有的明文块会在归还 Worker 缓冲池前安全擦除;借用或引用的外部输入永不被写回。

xrtTlsSessionWrite() 是应用明文写入入口。它按 TLS 记录上限拆分输入,并在 SendLimit 只容纳部分记录时返回 XTLS_OK 和实际短写长度;完全没有进展时才返回 XTLS_AGAIN。每条记录要么完整进入 Send 并消耗一个写序号,要么队列和序号均不改变。底层传输通过 xrtTlsSessionSendFront() 或 xrtTlsSessionSendSpans() 发送,再以 xrtTlsSessionSendConsume() 报告精确完成量。

xrtTlsSessionClose() 只排队一次 close_notify。收到对端 close_notify 时会话自动排队一次回应,等待本地密文排空后进入 CLOSED;重复调用不会产生重复 Alert。xrtTlsSessionPeerAlert() 可查询最后收到的 Alert。底层传输遇到 EOF 必须调用 xrtTlsSessionEof():只有此前收到认证的 close_notify 才是正常关闭,否则报告 XTLS_ERROR_TRUNCATED,避免把截断攻击误认为正常 EOF。

客户端或服务端发现协议失败时,会话会按根错误映射并尽力排队一次 fatal Alert:握手密钥尚未建立时使用明文记录,建立后使用当前写 epoch 保护。Alert 排队成功与否不改变失败结果;OOM、发送硬上限或已经结束的传输都不能覆盖最初的结构化错误。会话随后进入 FAILED,调用方仍可通过 SendFront / SendSpans 排空已经生成的 fatal Alert,再关闭底层传输。

xrtTlsSessionProtocol() 借用返回状态机已经严格确认的 ALPN 选择,视图有效到会话销毁。尚未协商或对端没有选择 ALPN 时返回 false,不设置错误,也不修改输出;空会话或空输出参数才是 XTLS_ERROR_ARGUMENT。应用可以在会话进入 READY 后查询,协议适配器也可以在内部握手阶段据此选择 HTTP/1.1、HTTP/2 或自定义上层协议。

xtlswait 是等待原因位集合:INPUT、OUTPUT、APPLICATION、IDENTITY 和 VERIFY 可以组合。公开状态只包含 NEW、HANDSHAKE、READY、CLOSING、CLOSED 与 FAILED,内部握手步骤不进入 ABI。XTLS_AGAIN 和 XTLS_CLOSED 是正常控制结果,不创建、清除或覆盖 xerror。

TLS 会话恢复对象

<xrt/tls_resume.h> 提供独立可裁剪的 xtlsresume,用于在 TLS 状态机、调用方缓存和后续连接之间传递恢复资产。对象创建时深拷贝 ticket、PSK、SNI、ALPN 和可选对端身份,一次精确分配后保持不可变;引用计数允许跨线程共享,最后一个引用释放前会清除整块内存。协议客户端不拥有全局缓存,应用可以直接保存对象,也可以在后续组合显式缓存策略。

当前对象契约只接受 TLS 1.3:ticket 必须为 1 到 65535 字节,PSK 长度必须等于密码套件摘要长度,寿命必须位于 1 到 604800 秒。ALPN 最多 255 字节,SNI 拒绝内嵌空字节;可选 PeerIdentity 是不参与线路编码的调用方身份域,可保存证书或信任策略摘要。xrtTlsResumeInfo() 发布的全部视图只在对象引用存活期间有效,其中 Secret 是敏感只读视图,调用方不得修改或无保护地记录。

有效期使用 [IssuedAt, ExpiresAt) 半开区间;墙钟回退到签发时刻以前时对象安全失效,不会延长票据寿命。xrtTlsResumeTicketAge() 先验证有效期,再按毫秒向下取整并与 AgeAdd 执行协议规定的 32 位模加,失败时不修改输出。对象层只表达材料和时间,不决定票据替换、容量、并发或淘汰策略。

对象生命周期示例位于 examples/tls/resume/main.c,下一连接 ClientHello 示例位于 examples/tls/client_resume/main.c;深拷贝、引用、时间边界、非法字段、OOM 和单头门禁位于 tests/tls/test_tls_resume*.c 与 tests/single/test_single_tls_resume.c。对象释放路径直接复用已经独立验证的 xrtSecureZero() 清零原语。

恢复对象可以直接交给下一条客户端连接。Resume 在创建期间由配置借用;创建成功后会话持有独立引用,因此调用方可以立即释放自己的引用:

省略 ServerName 和 ALPN 列表时,客户端从恢复对象精确继承两者。显式 SNI 必须完全匹配票据绑定;显式 ALPN 列表必须包含票据协议,票据未绑定 ALPN 时则不能为恢复连接额外提供协议。过期、尚未生效、套件被当前策略禁用或路由域不匹配的对象都在会话分配前拒绝。

TLS 客户端

<xrt/tls_client.h> 提供独立客户端构造入口,不把客户端字段塞进服务端配置,也不调用 socket。xtlsclientconfig 在创建期间借用共享上下文、SNI、ALPN 数组、可选 verifier 和可选恢复对象;创建成功后,会话持有这些共享对象的引用,并在同一块角色尾部内存中保存名称、协议名、临时密钥、扩展工作区和初始 ClientHello 的独立快照。

初始化配置使用默认共享策略、无 SNI 和无 ALPN;完整证书握手必须显式绑定 verifier,否则在创建时失败。构造器只发布当前裁剪构建可以执行的参数:套件需要对应 AEAD 和摘要调度,组需要密钥交换后端,签名需要本构建的验签后端。TLS 1.2/1.3 均已接入;TLS 1.2 要求 EMS 和 RFC 5746 的空初始绑定,不提供重新协商。Ed448 不会发布;P-521/SHA-512 不会出现在允许 TLS 1.3 的实际 offer 中。TLS 1.2-only 的 0x0603 是 SHA-512/ECDSA 组合,仍可用于已有 P-256/P-384 后端。纯 PSK 恢复构建没有验签后端时可以省略签名扩展,不因此新增密码算法。

创建成功会立即生成密码安全随机数、兼容 session ID 和首选可用组的一份 key share,并排队一条完整明文 ClientHello 记录。SNI、ALPN、版本、组、签名和 key_share 均使用公共 Hello writer 编码,不保留固定 1024 字节构建缓冲;发送队列仍受上下文 SendLimit 约束。此时会话进入 XTLS_STATE_HANDSHAKE,等待原因同时包含输入和输出,传输层可直接发送 xrtTlsSessionSendFront() 或 xrtTlsSessionSendSpans() 返回的密文。

xrtTlsClientDrive() 在上下文记录与握手预算内消费已经 Feed 的线路记录。普通 TLS 1.3 ServerHello 必须精确回显兼容 session ID,只能选择实际写入 ClientHello 的套件和 key share,并同时携带唯一的 supported_versions 与 key_share。ServerHello 可以跨任意数量的明文记录重组;兼容 CCS 只接受单字节 1,Alert、意外明文应用数据、尾随握手消息和未 offer 的选择都不会穿过状态机。

有效 ServerHello 先在临时状态中完成 transcript、ECDHE、handshake secret、双向 traffic secret 和记录 key/IV 派生,再消费记录并一次替换收发 epoch。任一步失败都会进入 XTLS_STATE_FAILED,不会发布半组密钥;成功后立即擦除客户端临时私钥,接收方向使用服务端流量秘密,发送方向使用客户端流量秘密。XTLS_AGAIN 只表示需要更多输入或后续阶段尚未推进,不覆盖线程错误。

握手密钥切换后,EncryptedExtensions 必须位于 TLS 1.3 受保护记录中。状态机允许 SNI 确认、supported_groups 和 ALPN,拒绝 early_data、未知扩展、未请求的 SNI/ALPN 和不在客户端真实线路 offer 中的协议。消息支持跨加密记录重组;一条记录内的后续握手消息不会被提前消费,而是以稳定记录偏移留给下一状态。只有整条 EE 通过解析、出现位置和 offer 校验后,transcript 与 ALPN 才会一起提交。

Certificate 使用严格公共 parser,拒绝非空请求上下文和客户端未请求的每个 CertificateEntry 扩展。状态机先精确测量完整线路链,再用一次分配保存证书视图与独立 DER;证书视图数组按目标 ABI 的 xx509cert 对齐,不能假设父对象尾地址天然满足 64 位字段对齐。链长度没有固定张数上限,只受握手消息和整数上限约束。信任验证和 transcript 更新都成功后才发布对端快照。CertificateVerify 必须使用 ClientHello 真实提供的方案,并以消息加入 transcript 之前的摘要验证 RFC 8446 上下文签名;自定义信任回调不能绕过此步骤。

启用客户端验证后,xrtTlsClientCertificateCount() 返回当前已经认证的链长度,xrtTlsClientCertificate() 按索引借用稳定到会话销毁的 xx509cert;原始 DER 位于 Certificate.Raw,不需要重复的 DER 指针和长度 API。握手尚未收到证书或本次使用 PSK 恢复时数量为零,因为恢复线路没有重传证书;按索引查询此时返回状态错误,不能把票据中的身份摘要伪装成证书。

CertificateVerify 通过后,客户端严格按服务端握手流量秘密校验 Finished。校验使用加入服务端 Finished 之前的 transcript;成功后先把服务端 Finished 纳入临时 transcript,再派生主秘密和双向应用流量秘密,并以仍在生效的客户端握手写 epoch 排队客户端 Finished。只有该记录完整进入有界 Send 队列后,状态机才一次提交 transcript、应用收发 epoch 和 XTLS_STATE_READY;输出空间不足返回 XTLS_AGAIN,不会消费服务端 Finished 或提前切换密钥。

进入 READY 后,同一个 xrtTlsClientDrive() 继续处理应用记录、Alert 与后握手消息。应用明文以 Scratch 块零拷贝移动到 Plain 队列;达到 PlainLimit 时返回 XTLS_AGAIN 并发布 XTLS_WAIT_APPLICATION,挂起记录不会重复解密或再次递增接收序列号。收到 close_notify 后客户端自动排队一次响应,忽略对端随后发送的应用数据,并在本地响应排空后进入 CLOSED。

TLS 1.3 KeyUpdate 必须独占一条记录。客户端收到 update_not_requested 时只替换读取 secret/key;收到 update_requested 时先用旧写 epoch 排队 not_requested 应答,再一次提交新收发 epoch。强制应答遇到 SendLimit 会保持活动 secret、写序列号和挂起记录不变,待输出排空后重试。应用也可以主动轮换写方向:

主动消息同样使用旧写 epoch,只有完整记录入队后才替换客户端写 secret/key;XTLS_AGAIN 不会让连接进入半更新状态。线路队列保证已经排队的旧 epoch 数据、KeyUpdate 和随后新 epoch 数据保持调用顺序。

启用 XRT_FEATURE_TLS_CLIENT_RESUME 后,客户端从包含客户端 Finished 的最终 transcript 派生 resumption master secret,再按每张 NewSessionTicket 的 nonce 派生独立 PSK。发布对象深拷贝 ticket、实际 SNI、实际协商 ALPN 和已验证叶证书 SHA-256 身份;MaxEarlyData 只保存服务端限制,不代表当前客户端支持发送 0-RTT。

ResumeLimit 默认为 4,可设为 0 禁用保存,最大为 64。队列槽位包含在客户端角色的单块尾部内存中,不为节点单独分配;满队列淘汰最旧票据并保留最新票据。xrtTlsClientResumeCount() 查询数量,xrtTlsClientResumeDropped() 统计显式禁用、容量淘汰和恢复对象 OOM,xrtTlsClientTakeResume() 按接收顺序把会话持有的引用转移给调用方。空队列返回 NULL 且不改变错误状态;对象缓存、目标选择和跨连接并发仍由调用方拥有。已进入 READY 的连接不会因为可选票据缓存 OOM 失败,该票据只计入丢弃统计且不会泄漏临时线程错误。

NewSessionTicket 支持跨记录重组和同记录多消息,畸形寿命、长度、空票据或已知扩展会使客户端失败。下一条 ClientHello 最多提供一张外部票据,同时发送 psk_dhe_ke、正常 X25519 key share 和位于扩展列表末尾的 pre_shared_key。binder 从最终编码的 ClientHello 截断 transcript、票据 PSK 和套件摘要独立计算;当前不发送 0-RTT。

服务端可以不选择 PSK,此时客户端安全回退到完整证书握手。服务端选择时只接受 identity 0,套件摘要必须与票据一致且仍必须提供有效 key share;接受后客户端跳过 Certificate/CertificateVerify,直接校验服务端 Finished,再按正常路径发送客户端 Finished 并切换应用 epoch。恢复连接的 ALPN 必须与票据绑定完全一致,后续新票据继承原来已经认证的 PeerIdentity。xrtTlsClientResumed() 在服务端 ServerHello 接受 identity 0 后返回 true;它报告协商选择,不把后续握手成功状态混入同一个查询。

当前门禁已经覆盖证书认证和 PSK+DHE 恢复型 TLS 1.3 客户端从 ClientHello 到持续 READY 的主路径:独立复算 binder、双方 Finished、应用流量秘密、resumption master 与 ticket PSK,验证恢复回退、错误 identity、缺失 key share、ALPN 绑定、双向应用数据、认证关闭、KeyUpdate 两种请求、主动更新、旧/新 epoch 顺序、票据分片/聚合/有界淘汰/深拷贝路由域、应用及输出背压、非法消息、目标 OOM、GCC、TinyCC x86 和单头文件。客户端同时支持带 extended master secret 的 TLS 1.2 ECDHE 证书完整握手、ALPN、双向应用数据与认证关闭;TLS 1.2 不提供会话恢复、重新协商或 KeyUpdate。非 ResumeOnly 配置缺少 verifier 时在创建期拒绝。客户端与服务端支持单次 HRR;TLS 1.3 应用写入会在记录用量临界点自动换钥。CertificateRequest、客户端证书认证和 0-RTT 未实现。对应门禁位于 tests/tls/test_tls_client.c、tests/tls/test_tls_client_server_hello.c、tests/tls/test_tls_server12.c、tests/tls/test_tls_client_oom.c、tests/single/test_single_tls_client.c 和 tests/single/test_single_tls_client_server_hello.c。

TLS 服务端

<xrt/tls_server.h> 提供独立服务端入口,不把服务端身份、SNI 路由或票据缓存塞进客户端配置,也不直接持有 socket。xtlsserverconfig 在创建期间借用共享上下文和静态身份;成功后会话持有引用并深复制 ALPN 列表。至少必须提供静态 Identity 或同步 Select,从而保证未知 SNI 或恢复回退仍有明确认证路径。

会话创建时不生成输出,只进入 HANDSHAKE 并等待 ClientHello。传输层把收到的密文交给 xrtTlsSessionFeed*(),调用 xrtTlsServerDrive(),再从公共 Send 队列发送服务端航班;应用数据、关闭、等待原因和所有权接口与客户端共享。TLS 1.3 完整证书航班依次生成 ServerHello、EncryptedExtensions、Certificate、CertificateVerify 和 Finished,验证客户端 Finished 后原子切换到应用 epoch 与 READY。TLS 1.2 路径要求 extended master secret,执行 ECDHE 证书完整握手并在 ChangeCipherSpec 边界原子切换记录 epoch;它支持 SNI、ALPN、双向应用数据和认证关闭,不提供会话恢复、重新协商或 KeyUpdate。

服务端首航的扩展表、证书条目、签名输入和临时编码流来自会话内的惰性临时 Arena。 空闲连接和仅创建未握手的会话不分配 Arena 块;首航完成后立即安全清零并释放全部块, 不会为每个连接保留固定握手缓冲。TLS 1.2 与 TLS 1.3 复用同一个 Arena 和清理路径, 错误、OOM 与销毁也执行相同的安全释放;最终需要排队发送的记录仍由公共 Send 队列 独立拥有,不借用已经清理的临时内存。

Select 在 ClientHello 完成严格解析、SNI/ALPN 提取之后,任何服务端输出生成之前执行。请求中的 SNI 和完整 ALPN 扩展负载只在回调期间借用;xtlsserverchoice 初始包含静态身份、按服务端偏好计算的协议下标和零值 Cookie,回调可以替换身份或协议,并写入一个 XRT 不解释的 64 位宿主路由标识。返回身份按共享对象处理,选择结果在回调返回后由会话持有。xrtTlsServerCookie() 在成功选择后返回该标识,未设置或选择尚未发生时返回零,适合让传输适配层把已完成握手的连接关联回选择身份时的配置代。XTLS_SERVER_PROTOCOL_NONE 表示不协商 ALPN;RequireProtocol 为 true 时没有共同协议会明确失败。回调上下文只借用到首航完成,不能递归驱动同一会话。

xrtTlsServerName() 返回服务端从 ClientHello 深复制的 SNI,视图稳定到会话销毁;xrtTlsSessionProtocol() 返回最终 ALPN。进入 READY 后,xrtTlsServerKeyUpdate() 与收到的 KeyUpdate 都遵循“旧 epoch 完整排队,新 epoch 一次提交”的顺序,发送背压和分配失败不会发布半更新状态。

启用 XRT_FEATURE_TLS_SERVER_RESUME 后,Resume 回调接收 SNI、完整 ALPN 负载、不透明 ticket 和客户端计算的混淆年龄。回调返回借用的不可变 xtlsresume;会话在回调返回后立即增加引用,再校验版本、套件、SNI、ALPN、有效期和年龄容差。未找到票据或路由绑定不匹配会安全回退到完整证书握手;已经匹配同一票据元数据但 binder 错误属于认证失败,必须发送 fatal Alert,不能降级绕过认证。当前恢复始终保留 ECDHE key share,只支持 PSK+DHE,不支持纯 PSK 或 0-RTT。

READY 服务端使用 xrtTlsServerTicket() 把调用方给出的非空不透明 ticket 和寿命编码为 NewSessionTicket,并把对应服务端恢复对象的所有权交给调用方;xrtTlsServerTicketNew() 使用 32 字节密码安全随机 ticket 和 86400 秒默认寿命。XRT 不维护进程全局票据缓存,调用方可以按租户、容量、过期和持久化需求选择 Map、分片缓存或外部存储。发送硬上限会在随机数、派生和分配之前预检;返回 XTLS_AGAIN 时输出对象为 NULL,写序号、traffic secret 和恢复状态均不变,排空输出后可原样重试。

服务端门禁覆盖随机小分片 TLS 1.2/1.3 证书握手、SNI/ALPN 动态选择、双向应用数据、认证关闭、TLS 1.3 主动和被动 KeyUpdate、明文与受保护 fatal Alert、票据签发、第二连接 PSK+DHE 恢复、未知或过期票据回退、SNI/ALPN/年龄绑定、坏 binder、畸形 PSK 扩展、发送背压、定向 OOM 和单头文件。完整示例位于 examples/tls/server/main.c,测试位于 tests/tls/test_tls_server*.c 与 tests/single/test_single_tls_server.c。已支持单次 HRR 和 TLS 1.3 应用写入自动换钥。当前没有客户端证书认证、0-RTT、TLS 1.2 会话恢复、重新协商或异步身份选择;TCP 组合入口由后文独立的 tls_stream 裁剪单元提供。

TLS 对端验证

<xrt/tls_verify.h> 把信任决策从客户端状态机和 X.509 原语之间独立出来。xrtTlsVerifierCreate() 深复制可选 xx509store,因此创建后可以释放或修改来源 store;验证器本体不可变、引用计数共享,并要求自定义回调可以并发执行。没有回调时必须提供至少一个 trust anchor,空 store 会在创建期失败。

xrtTlsVerifierRetain() 增加共享引用,xrtTlsVerifierRelease() 释放引用并在最后一次释放时销毁快照。xrtTlsVerifierVerify() 使用配置的时间源、信任决策和附加策略完成一次对端验证;同一个验证器可以被多个 TLS 会话并发调用。

默认 xrtTlsPeerVerify() 使用调用方显式给出的时间验证路径,并按角色检查 digitalSignature KeyUsage 和 serverAuth / clientAuth EKU;服务端角色还按 RFC 9525 匹配请求名称。线路链按叶到根排列,trust anchor 来自 store 且不要求出现在对端链中。调用方可以传入 xtlsverifypolicyproc,在这些默认检查全部成功后读取 xtlsverifiedpeer:Path 按叶到根排列但不包含独立 Anchor,全部视图只在回调期间借用。策略可以直接组合 CRL、OCSP、CT、证书固定或企业规则,不必重复建链和身份验证。

TLS 核心不会隐式加载系统根、下载 CRL、发起 OCSP 请求或提交 CT 查询;数据加载和联网属于独立可裁剪组合层,基础客户端不会在验证期间偷偷阻塞文件或系统 API。同步策略返回 false 时可以设置结构化错误,验证器会把它包装为 XTLS_ERROR_VERIFY 的 cause;没有设置错误时得到明确的 XERR_PERMISSION 拒绝。

xtlsverifydecision 是自定义 xtlsverifyproc 的完整结果域:XTLS_VERIFY_ACCEPT 接管信任,XTLS_VERIFY_REJECT 明确拒绝,XTLS_VERIFY_DEFAULT 回退到不可变 store,XTLS_VERIFY_ERROR 报告本次回调产生的结构化错误。回退时没有 store 会明确失败;错误结果必须在本次回调内设置错误,验证器隔离回调前后的错误槽并把本次错误保留为 cause。接受结果不会再调用只适用于默认路径的 Policy,但仍不能跳过 CertificateVerify 和 Finished。

xtlsverifytimeproc 为路径和附加策略提供同一个确定时间;省略时使用 xrtNow()。Verify、Policy、Time 可以并发调用,不能依赖线程局部的可变共享状态。配置的 Context 只在 xrtTlsVerifierCreate() 成功后转移所有权;最后一个引用释放时,xtlsverifyreleaseproc 恰好调用一次。

xrtTls13CertificateVerifySignature() 是公开的协议验签原语。它构造标准的 64 个空格、角色上下文、零分隔符和 transcript hash,严格区分 rsae / pss 密钥、P-256 / P-384 方案和 Ed25519,并拒绝 TLS 1.3 禁止的 PKCS#1 方案。它不执行证书路径验证,调用方若直接使用原语必须先完成信任决策。

完整示例位于 examples/tls/verify/main.c;基础契约、默认真实 RSA 路径、CRL 路径策略、OOM、客户端端到端航班、TinyCC x86 和单头门禁分别位于 tests/tls/test_tls_verify*.c、tests/tls/test_tls_client_server_hello.c 与 tests/single/test_single_tls_verify.c。tests/tls/test_tls_verify_policy_rsa.c 同时展示无 CRL、空 CRL、吊销和过期 CRL 的完整组合方式。

TLS 身份

<xrt/tls_identity.h> 提供与网络和角色状态机解耦的共享身份对象。身份创建时深复制完整 DER 证书链,第一张证书必须是叶证书;xrtTlsIdentityCertificate()、xrtTlsIdentityPublicKey() 返回的借用视图一直有效到最后一个身份引用释放。身份创建后不可变,可由多个服务端配置和 Worker 并发共享。

构造器私钥输入验证
xrtTlsIdentityRsa()PKCS#1 或未加密 PKCS#8 DER模数、指数、全部 CRT 参数、叶 SPKI 和 RSA-PSS 限制
xrtTlsIdentityP256()32 字节标量、SEC1 或未加密 PKCS#8 DER标量范围、曲线 OID、SEC1 可选公钥和叶 P-256 SPKI
xrtTlsIdentityP384()48 字节标量、SEC1 或未加密 PKCS#8 DER标量范围、曲线 OID、SEC1 可选公钥和叶 P-384 SPKI
xrtTlsIdentityEd25519()32 字节种子、单/双层 DER OCTET 或 RFC 8410 PKCS#8算法参数缺省、派生公钥和叶 Ed25519 SPKI

构造器不借用私钥输入。内置身份与证书链保存在一块紧凑分配中,释放前安全清零;RSA 保留完整 CRT 视图,Ed25519 保留一次展开的签名密钥,避免每次握手重复派生。PEM、文件和系统证书库不属于身份核心依赖,调用方可先用对应层取得 DER,后续再由独立便捷加载模块组合。

xrtTlsIdentityCanSign() 同时检查 TLS 版本、证书身份类型、标准签名方案和后端限制。TLS 1.3 不会接受 RSA-PKCS#1 CertificateVerify,ECDSA 方案必须与 P-256/P-384 证书曲线一致;TLS 1.2 的 ECDSA 线路值仍按摘要/签名对解释,因此两条已实现曲线都可按对端选择使用 SHA-256、SHA-384 或 SHA-512。rsaEncryption 与 RSASSA-PSS 证书分别只进入 rsa_pss_rsae_* 和 rsa_pss_pss_* 路径。受限 RSA-PSS 密钥还必须满足证书与私钥两侧的摘要、MGF1、最小盐长和 trailer 约束;没有任何共同 TLS 方案的身份在构造时直接拒绝。

xrtTlsIdentitySign() 接收完整 TLS 待签内容。空输出查询精确签名长度,容量不足时不会调用签名器;内置后端通过临时结果或底层原子发布契约保证失败不发布部分签名。RSA-PSS 使用密码安全随机盐,ECDSA 按线路摘要使用 RFC 6979 确定性 low-S 签名,Ed25519 使用纯 Ed25519 模式。

xrtTlsIdentityCreate() 是真实扩展接口,用于 HSM、系统密钥库或远程签名器。配置中的证书链仍由 XRT 深复制;签名上下文只在创建成功后转移所有权,最后释放时调用 Release。Supports 与 Sign 必须允许并发调用;Supports 是不设置错误的能力谓词,Sign 失败时必须保持输出和长度不变。没有硬件或系统密钥需求时应使用内置强类型构造器。

身份层错误使用 xrt.tls 的 identity 错误码,并把 DER、X.509 或 crypto 失败保留为 Cause。因此上层宿主可以按 TLS 阶段映射,C 调用方也能继续读取底层格式或密码原因。

裁剪宏相互独立:

  • XRT_FEATURE_TLS_IDENTITY:共享对象、证书快照、引用和外部签名器。
  • XRT_FEATURE_TLS_IDENTITY_RSA:RSA PKCS#1/PKCS#8、PKCS#1 与 PSS 签名。
  • XRT_FEATURE_TLS_IDENTITY_EC:共享 SEC1/PKCS#8 EC 格式解析,不单独发布构造器。
  • XRT_FEATURE_TLS_IDENTITY_P256、XRT_FEATURE_TLS_IDENTITY_P384:各自曲线身份。
  • XRT_FEATURE_TLS_IDENTITY_ED25519:Ed25519 身份。

完整示例位于 examples/tls/identity/main.c;模块化、负向、OOM、组合和单头门禁位于 tests/tls/test_tls_identity*.c 与 tests/single/test_single_tls_identity*.c。

TLS-over-TCP 组合流

<xrt/tls_stream.h> 以 XRT_FEATURE_TLS_STREAM 独立裁剪,把公开 TCP Stream 与公开 TLS 客户端/服务端会话组合成事件驱动明文字节流。它不复制握手、验证、身份或记录状态机,也不让 TLS 原语反向依赖 socket;高级用户仍可直接使用传输无关会话层。

客户端连接数字地址使用 xrtTlsStreamConnect()。已经通过代理、自定义拨号或 明文协议协商得到 TCP Stream 时,在其所属 Worker 上调用 xrtTlsStreamClient();它成功后接管 TCP 引用,后续只从 TLS Stream 读写。 需要自行创建角色 Session 的底层用户可以调用 xrtTlsStreamAttach(),成功时 同时转移 Session 与 TCP 引用。服务端在 TCP Accept 回调内调用 xrtTlsStreamAccept(),并把其布尔结果直接作为 Accept 结果返回:

xrtTlsStreamAccept() 成功后接管 TCP Accept 交付的调用方引用;失败时保持原 Accept 失败回收规则。xrtTlsStreamAttach() 和 xrtTlsStreamClient() 只接受 已经发布 Open、尚未读结束、写结束、Close 或 Abort 的双向 Stream,必须在该 Stream 的 Worker 上调用。调用方必须在升级点停止直接收发和改变 TCP 状态。 失败时输出清空,不接管输入引用,不改变 Session 的缓冲池归属,也不替换 TCP 事件或用户数据;修正配置后可用同一 Session 与 Transport 重试。它们会立即 处理 TCP 缓冲中已有的密文,因此代理响应后的尾随 TLS 记录不会丢失。成功时 ppStream 返回独立调用方引用。 xrtTlsStreamConnect() 成功同样返回调用方引用。xrtTlsStreamDestroy() 只释放 引用,不隐式关闭;运行时引用会保持对象活到唯一 Close 回调结束。

完成握手后,可以在所属 Worker 上调用 xrtTlsStreamSetEvents(pStream, pEvents, pData) 原子替换事件表和用户数据。 它用于 HTTPS Upgrade、WebSocket 和应用协议协商后的处理器接管;不会再次发布 Open,也不会重放切换前已经留在明文缓冲中的数据。接管层必须在切换点显式 取得并处理协议余量。原始 TCP 仍由 TLS 组合流独占,不能借此绕过 TLS 接管。

xrtTlsStreamData() 以 acquire 语义返回当前用户数据的借用指针快照;事件切换 在所属 Worker 上保持顺序,但查询可以来自任意线程。快照不延长目标生命周期, 指针所指对象的存活期与并发访问仍由调用方管理。xrtTlsStreamRef() 在引用计数 耗尽时返回空并设置状态错误,不会让已经释放或饱和的对象重新进入生命周期。

需要主机名时启用 XRT_FEATURE_TLS_STREAM_DIAL 并调用 xrtTlsDial()。该可选层 直接复用 xrtNetDial() 的 Resolver、双栈候选竞速、回退、取消和统计,不复制 另一套 DNS 或 TCP 连接器。数字地址使用 xrtTlsStreamConnect();已有 TCP Stream 使用 xrtTlsStreamClient() 或 xrtTlsStreamAttach();服务端 Accept 使用 xrtTlsStreamAccept();完全自定义传输仍可直接使用会话层。

启用 XRT_FEATURE_TLS_STREAM_DIAL_PROXY 时,使用 xrtTlsDialProxy() 明确选择代理 路径。目标主机和端口先用于 CONNECT,隧道建立后再对同一目标执行 TLS;直连入口 不会读取代理配置,代理入口也拒绝空代理。两条入口共享同一个 TLS Dial 状态机、 终态门、错误链和统计接口。Config.Timeout 是覆盖代理端点解析、TCP、CONNECT 和 TLS 握手的唯一全过程期限;代理组合层不会再启动一只竞争的总定时器,但 Config.Transport 中的各阶段限制仍然生效。取消或总超时在 CONNECT 已完成后会 继续中止 TLS Stream,不会停留在永久 HANDSHAKE 状态。

Config.Transport 控制 DNS、地址族、Happy Eyeballs、候选数和 TCP 阶段超时;Config.Stream 控制 TLS 握手、认证关闭和缓冲硬上限;Config.Timeout 是从提交开始覆盖 DNS、TCP 与 TLS 的全过程硬上限,零值表示只使用各阶段超时。ServerNameFromHost 默认开启,仅在 Tls.ServerName 为空时用 sHost 填充 SNI 与证书名称;显式名称始终优先,关闭该选项则不做自动填充。

通过配置校验后,受管 TLS Dial 会先取得一份 Engine 初始化租约,再创建客户端会话、组合 Stream、全过程 Timer 和底层 TCP Dial。只有这些活动对象已经接管 Engine 生命周期后,临时租约才会释放;任一创建步骤失败则在返回前完整回滚并保留原始 cause。并发 xrtNetEngineDestroy() 不能跨过这段尚未返回对象的初始化窗口。

成功时,最终 Stream 的用户 Open 先执行,随后 xtlsdialproc 以 XNET_RESULT_OK 交付同一 Stream 的调用方引用。握手完成前失败只发布一次 Dial 完成回调;半初始化 TLS Stream 在回调前释放,也不额外调用用户 Stream Close。xrtTlsDialCancel() 可以在解析、TCP 连接或 TLS 握手阶段竞争取消;返回真表示取消已经赢得唯一终态门,之后不能再发布成功。安全 Stream 已赢得发布权或 Dial 已终止时返回假。终态由 xrtTlsDialState() 与 xrtTlsDialError() 固定保存;底层 TCP Dial 已经终结 Engine 活动占用,但其只读快照保留到 TLS Dial 销毁,因此 xrtTlsDialTransportStats() 在 TLS 阶段和终态继续提供候选、尝试、失败和获胜地址统计。

启用 XRT_FEATURE_TLS_STREAM_DIAL_FUTURE 后,可以用同一受管拨号状态机直接取得 Future:

成功 Future 持有一个已经 OPEN 的 xtlsstream 引用,用户 Open 回调仍先于 XFUTURE_RESOLVED 发布。xrtFutureValue() 返回借用指针;需要在销毁 Future 后 继续使用时,先调用 xrtTlsStreamRef() 保留独立引用。Future 值析构只释放它所 持有的引用,不隐式关闭其他调用方引用。

xrtFutureCancel() 把协作取消传递到当前 DNS、TCP 或 TLS 阶段,只有底层完成 清理后 Future 才进入 XFUTURE_CANCELLED。连接失败与全过程超时进入 XFUTURE_FAILED,xrtFutureError() 保留 TLS 包装错误和底层原因链。需要观察 候选统计或在完成前查询阶段时继续使用回调式 xrtTlsDial();Future 入口是常见 连接路径的轻量适配器,不复制 Dial 状态机。

Open 只在 TCP 已连接、TLS 已到 READY 且 SNI/ALPN/证书验证全部完成后发布。客户端与服务端事件都在底层 TCP 所属 Worker 上串行执行。Send、SendBound、Buffer、Read、Consume 和 Session 必须在该 Worker 上调用;Close、Abort、State、Available、Pending、Transport、Data 和终态 Error 支持并发调用或快照读取。

Send 允许成功短写。XTLS_OK 且 iWritten < Size 表示该前缀已原子受理,剩余数据由调用方保留;XTLS_AGAIN 保证 iWritten == 0。xrtTlsStreamSendVec() 先校验全部 Span 与总长度,再直接逐片生成记录,不分配一块拼接副本;iWritten 是跨 Span 的连续受理前缀,输入无效或总长度溢出时保持为零且不会修改会话。两种发送入口使用相同的背压契约。

xrtTlsStreamSendBound() 在所属 Worker 上返回一次 Send 对指定明文产生的精确密文线路长度,包含每条记录的头、显式 nonce、TLS 1.3 内层类型和认证标签。它不修改会话、序列号或发送队列;零长度返回零,算术溢出或输出与 Stream/Session 重叠时失败且不修改输出。协议适配层可用它在接受明文前把自身队列预算换算成真实 TLS 线路成本。

背压会登记一个边沿,TLS 发送队列和 TCP 队列重新具备容量后发布 Writable。应用必须在 Writable 中从未受理偏移继续发送,不能重发已经计入 iWritten 的前缀。每次至少受理一个字节后会登记 Drain;只有 TLS 密文队列与 TCP 用户态发送预算同时归零才发布该事件。Writable 与 Drain 均不会重入尚未返回的 xrtTlsStreamSend() 或 xrtTlsStreamSendVec(),因此调用方可以在返回后再统一提交 iWritten。

xrtTlsStreamPending() 返回 TLS Session 尚未转移的密文与底层 TCP 用户态发送 队列的饱和相加快照。它不包含已被操作系统接受的内核缓冲字节,也不代表对端 已经读取;成功短写后允许立即为零。该查询可从任意线程用于统计和限流, Drain 回调发布时它必须为零。

适配器要求 TCP WriteLimit >= TLS SendLimit,从而把一批完整 TLS 密文块链全有或全无地转移给 TCP。常规路径不复制密文;TCP ReadBuffer 可以整体移动进 TLS Feed。当一次 TCP 输入大于 Feed 剩余容量时,只复制可容纳的前部 Span,消费后继续,避免以固定 8 KiB 缓冲或无界增长掩盖压力。成功短写与同步 TCP 低水位回调之间有重入门;同步产生的 Writable/Drain 会转为同一 Worker 的内部命令,外层发送返回后才允许进入应用。

Read 回调借用 const xnetbuf*。应用可以用 xrtTlsStreamRead() 复制并消费,也可以检查 xrtTlsStreamBuffer() 后以 xrtTlsStreamConsume() 精确确认已处理字节。默认通知采用受控边沿语义:当前明文没有全部消费前暂停新的 TCP 接收。增量协议解析器在保留不完整前缀时可调用 xrtTlsStreamReadMore();TLS 会在 PlainLimit 内继续解密,只在明文增长后再次发布 Read,并要求限制中仍能容纳一条最大明文 record,避免有空间但无法取得下一条完整记录的永久停滞。请求待完成期间不能替换事件接收者。xrtTlsStreamPullup() 只按需连续化精确前缀,不消费明文。借用不能保存到下一次回调;消费到零后恢复普通残留密文处理和底层读取。

裁剪与依赖

层裁剪宏依赖能力
Future 核心XRT_FEATURE_FUTUREcancel结果、所有权、同步等待、协作取消
Future 适配桥XRT_FEATURE_FUTURE_BRIDGEfuture、thread、atomic异步操作、Promise 与取消监听的一次性装配
Future 延续XRT_FEATURE_FUTURE_CONTINUEfutureContinue、Then、Catch、Finally 与安全结果透传
Future 组合器XRT_FEATURE_FUTURE_COMBINEfutureAny、All、Race 与源结果保活
协程等待桥XRT_FEATURE_FUTURE_COROUTINEfuture、coroutine_scheduler不阻塞调度线程的 await

延续链、线程池执行器和结构化任务组属于后续独立层,不会增加只需要 Future 核心的程序体积。

统一等待边界

当前契约不再提供 xwaitsrc 或网络专用 xnetwaitsrc。异步操作直接返回同一种 xfuture:Future 同时携带终态、成功值、结构化错误、取消入口和所有权,因此线程 等待、协程 Await、延续、组合器和 TaskGroup 不需要先把它包装成另一个等待对象。

TCP 可读、可写、Drain、Accept 等只表示条件就绪的场景仍通过对应的 xrtNet*WaitAsync 返回 Future;真正接收数据或建立连接的操作返回拥有结果的 Future。 这种分层保留“只观察条件”和“取得结果”两类能力,但不复制 bool、xnet_result 和 Future 终态三套互相转换的状态模型。

需要组合不同对象类型时,上层只保存 xfuture*,通过 Any、All、Race 或 TaskGroup 协调;完成后按创建该 Future 的操作契约解释值。底层事件端口的 readiness/completion 观察仍属于网络引擎内部原语,不作为跨模块类型擦除接口泄漏到业务层。

状态

xfuturestate 只有五个稳定状态:

  • XFUTURE_PENDING:生产端尚未写入结果。
  • XFUTURE_RESOLVED:成功,可读取 Value。
  • XFUTURE_FAILED:失败,可读取不可变 xerror。
  • XFUTURE_CANCELLED:生产过程确认以取消结束。
  • XFUTURE_CLOSED:最后一个生产端在未完成时离开,或显式关闭。

等待一个失败、取消或关闭的 Future 仍返回 XWAIT_OK,因为等待动作已经完成。调用方随后通过 xrtFutureState 或 xrtFutureResult 判断操作结果。XWAIT_TIMEOUT 和 XWAIT_CANCELLED 只描述等待者自身提前停止等待。

创建与生命周期

创建成功后,调用方各拥有一个 Promise 和 Future 引用。pParentCancel 可以为空;非空时,新 Future 的取消令牌继承父链。xrtPromiseRef、xrtFutureRef 分别增加端点引用,xrtPromiseDestroy、xrtFutureDestroy 分别释放。

最后一个 Promise 引用在 Pending 状态释放时,Future 自动进入 XFUTURE_CLOSED 并请求取消,避免消费者永久等待。已经完成的 Future 不会因 Promise 释放而改变状态或取消令牌。

Future 的内部完成监听按注册顺序执行。嵌套完成会追加到当前 Fiber 或线程的无分配派发队列,由最外层完成调用迭代排空;因此组合器和后续延续链的级联深度不会转化为 C 调用栈深度。

无分配终态监听

xfuturewatch 是调用方提供存储的底层终态监听节点,使用 XRT_FUTURE_WATCH_STORAGE_SIZE 固定不透明空间,不为每次监听分配内存。 xfuturewatchproc 接收通知上下文;可选的 xfuturewatchreleaseproc 在节点被 Future 接管后,于完成通知或成功摘除时执行一次。

xrtFutureWatchAdd 返回 XFUTURE_WATCH_PENDING 时,Future 已接管节点;返回 XFUTURE_WATCH_READY 时源已经完成,节点仍归调用方且不会执行 Release; XFUTURE_WATCH_ERROR 表示参数或状态无效。xrtFutureWatchDetach 只尝试摘除尚未 开始通知的节点,成功时同步执行 Release;xrtFutureWatchRemove 还会等待已经开始的 通知结束,不能从该 Watch 自己的通知中调用。xfuturewatchresult 只描述注册结果, 不替代 Future 的终态。

取消

xrtFutureCancel 只发出协作取消请求,Future 保持 Pending,直到生产端完成、确认取消或关闭。这样资源回收、内核 I/O 取消和任务退出不会被一个伪造终态掩盖。令牌访问函数返回新增引用,使用完毕后调用 xrtCancelDestroy。

生产过程可以处理取消后正常返回值;取消请求与最终状态是两个正交事实。只有 xrtPromiseCancel 才写入 XFUTURE_CANCELLED。

xrtPromiseCancel、xrtPromiseClose 和最后一个 Promise 引用的隐式关闭使用两阶段发布:先独占终态写入权并完成取消令牌通知,再发布 CANCELLED/CLOSED、唤醒等待者和执行 Future 回调。因此任何已经观察到取消或关闭终态的线程都必然也能观察到令牌已经请求;令牌监听执行期间 Future 仍显示为 Pending,但其他生产者已经不能抢占终态。取消监听不得等待同一个 Future 进入终态,否则会形成生产端自等待。

同步等待

时间单位统一为毫秒,截止时间使用单调时钟。等待支持任意数量线程,并对虚假唤醒、完成与超时竞争、取消监听注销竞争进行循环检查。Future 终态与外部取消回调在同一把 Future 锁下线性化:终态先取得锁时返回 XWAIT_OK;取消先取得锁时,本次等待固定返回 XWAIT_CANCELLED,即使 Future 在等待线程恢复前已经完成也不会覆盖该结果。截止时间在相同循环边界检查,Future 尚未终结时到期返回 XWAIT_TIMEOUT。调用方取消只停止当前等待,不取消 Future;需要同时取消生产过程时另行调用 xrtFutureCancel。

协程等待

这些函数只能在 xcosched 管理的协程中调用。等待节点由当前协程栈保存,不产生每次 await 堆分配;Future 可由任意线程完成,完成通知通过内部代际令牌投递回所属调度器。通知早于真正 park 时不会丢失,等待退出后也不会污染下一次 park;独立的公共 xrtCoWake 不会被 Await 清理过程误消费。提前唤醒、超时和协程取消都会安全摘除等待节点。

协程取消使 Await 返回 XWAIT_CANCELLED,但不改变被等待 Future 的状态,也不自动决定当前协程的终态。用户过程可以完成清理后正常返回;只有显式调用 xrtCoConfirmCancel,当前协程才以 XCORO_TERM_CANCELLED 终结。对于一次调用者独占的网络 Future,放弃操作时应先调用 xrtFutureCancel,让 TCP/UDP 等待节点及时摘除;共享 Future 则由拥有生产过程取消权的一层决定。

组合器

组合器只协调多个 Future 的生命周期,不复制、不接管源值,也不把某个源的失败冒充为组合器自身失败:

  • Any 在第一个源进入任意终态时,以 xfuturepick 成功完成,不改变其余源。
  • Race 与 Any 的选择结果相同,随后向其余源发出协作取消请求;源生产端仍决定各自最终状态。
  • All 等全部输入槽位进入终态后,以保序的 xfutureall 成功完成。重复源按重复槽位计数;空集合立即成功完成。
  • 输入数量同时受连续分配大小和内部引用计数上限约束,超出任一边界都会在读取输入数组前以 XERR_RANGE 失败。
  • xfuturepick 和 xfutureall 都是借用视图,只在持有组合 Future 引用时有效。组合器内部保留全部源引用,因此通过视图读取源结果不会悬空。
  • 取消尚未完成的组合 Future 会确认组合器自身为 XFUTURE_CANCELLED、摘除全部源监听,并向源发出取消请求;源 Future 不会被伪造为已取消。

这种契约把“多个操作何时达到协调点”和“每个操作以什么结果结束”分开。调用方可以直接检查 pPick->Future 或 pAll->Futures[i] 的状态、值和结构化错误,也可以在上层任务组中实现失败即取消、收集全部错误等策略。

Future 桥

桥解决"异步操作先返回 Future、结果稍后才到"的装配竞态:Ready 之前的终态写入被挂起,Ready/Fail 决定放行或回收。

TLS 监听器

监听器把 TCP 绑定与异步接入组合为单入口;Accept 三形态覆盖 pull(非阻塞/Future/阻塞)消费模型。

TLS 拨号

拨号把 DNS 解析、TCP 竞争连接与 TLS 握手组合为一次异步操作,提供回调式与 Future 式两个入口。

TLS Stream

TLS Stream 组合 TCP 传输与 TLS 会话为单一明文接口:Worker 专用同步收发(Send/Buffer/Pullup/Read/Consume)与任意线程 Future 收发(SendAsync/RecvAsync/WaitAsync)双模型。

Watch 与聚合器的物理拥有图

xrtFutureWatchInitTraced(watch, notify, release, data, trace) 在初始化时固定 release(data) 所释放的强拥有槽;release 和 trace 必须非空。唯一拥有的上下文 折叠到源 Future 的边,共享引用计数上下文必须作为独立物理节点报告。初始化不 分配、不消费、不执行回调;只有 WatchAdd 返回 PENDING 才转移注册释放权。 READY / ERROR 时仍由调用方释放数据。公开 Watch 继续为 64 字节,不改变旧 Init。

Future Inspect 遍历已描述的挂接 Watch;任一未知 waiter 仍拒绝整个检查,不能 假装其数据是叶子。注册、通知、释放、图检查与之后提交之间仍需要调用方保证 整个图静止和所有回调代码驻留;本接口不建立安全点,不自动使语言 frame 可检查。

Any / All / Race 的共享上下文现在按实际 RC 身份入图:每个 Source 槽各一条边, 重复输入保留重复边;Pick 和 All 仅借用这些槽,不产生第二组虚假拥有边。挂起时 每个注册 waiter 持有一个 group 引用;输出另以真实 producer 引用拥有 group, group 又拥有 Promise。拥有型取消监听保有一个实际 group 引用,令牌链表仍只 借用监听。终态原生结果通过策略化原子发布保有 group;终态时取消监听实际注销, 不再留到原生结果销毁。未认证映射上下文仍不被自动准入为可收集节点。 未知源载荷/源 waiter 继续导致拒绝;图检查本身不执行通知、取消、析构或收集。

验证入口 tools/check_future_waiter_ownership.ps1 分别覆盖 Watch 的 PENDING / READY / detach 所有权、重复输入、取消/空集合、原生别名及分配失败原子性。

示例

网络适配层直接返回同一种 xfuture。TCP、UDP、TLS Stream、HTTP 和 WebSocket 的 Future 都可以交给上述同步等待、组合器、延续和协程 Await;协议层只定义 成功值、取消点和终态映射,不复制 Future 或协程状态机。

标准库和扩展库的异步适配器共用公开的 xfuturebridge;对象使用 XRT_FUTURE_BRIDGE_STORAGE_SIZE 字节不透明存储,可直接嵌入操作上下文。底层操作可以 在监听安装期间并发完成,但只有装配进入 READY 后才能发布 Future;监听分配 失败会进入 FAILED,底层晚到结果只执行对应资源回收,不会留下回调。这个底层 契约由 tests/concurrency/test_future_bridge_oom.c 独立验证,DNS、TCP、TLS 与 HTTP 适配器不再分别复制这一段易错生命周期代码。

xrtFutureBridgeCreate 创建 Future/Promise 对;xrtFutureBridgeInit 则借用一个 已有 Promise,两种路径都不接管 Promise 所有权。适配器最终仍须调用 xrtPromiseDestroy(xrtFutureBridgePromise(&bridge))。xrtFutureBridgeReady 与 xrtFutureBridgeFail 只能发布一次,重复发布以状态错误失败。完成回调先调用 xrtFutureBridgeWait 跨过极短装配窗口,再调用 xrtFutureBridgeUnwatch 与正在 执行的取消回调汇合。Wait 返回 false 时只回收底层结果,不写入 Promise。

适配器发布终态前必须先固定成功值或错误引用、注销取消监听、销毁底层操作, 并归还等待节点、缓存预算、网络对象引用和临时 Engine 租约。因而观察到网络 Future 终态时,适配器上下文不会再被迟到回调访问,也没有只为该操作保留的 Engine 活动对象。成功 Future 明确拥有的 Stream、Packet、响应或连接是公开结果, 其生命周期继续由 Future 引用保护,不属于内部临时资源。协作取消同样遵守该屏障, 不会先伪造 CANCELLED 再在后台回收操作。

完整示例位于 examples/concurrency/future/main.c、 examples/concurrency/future_combine/main.c、 examples/concurrency/future_coroutine/main.c 与 examples/tls/stream_future/main.c、examples/tls/dial_future/main.c。 其中 future_combine 同时演示 Any、All、Race、胜出源索引和 Race 的协作取消语义。

显式结果生命周期与物理控制块接管

xrtPromiseResolveOwnedPolicyV1 在发布时绑定不可变的 xfuturepayloadownershipv1 { size, Drop, Trace }。该策略与代码必须比所有结果 活得更久,context 固定为空;失败不消费结果。它与旧的 xrtPromiseResolveOwnedTraced 不同:提供追踪函数并不自动获得生命周期认证。

回收器在同一次全图 freeze 中、调用 Count/Trace 之前,用 xrtFutureOwnershipAdapterV1 及明确的策略身份白名单接纳物理控制块。 Future 与 Promise 是同一个节点,生产端引用不能凭估计扣除。未认证的拥有 载荷、正在完成的控制块、所有已注册 waiter,以及有取消观察者的令牌均拒绝。 转发 Owner、错误 Cause、取消 Parent 和载荷内的拥有边仍需逐一独立接纳。

Claim 可在提交前恢复;Clear 仅在最终重验的同一个 freeze 中隔离结果。 无观察者的 pending 控制块此时进入 CLOSED 并请求其自身取消,不通知用户代码。 真实结果、Owner、Error、Cancel 引用延迟到 freeze 外的 Finish 释放;重复 Finish 不重复消费。已 Clear 的控制块拒绝新增 Future/Promise 引用和结果读取。 普通终末释放仅对显式策略退出自身 mutation 后执行 Drop;旧回调保持保守隔离。 这不是对 pending task、Watch、原生回调或任意模块卸载的普遍授权。

裁剪与依赖

宏依赖能力
XRT_FEATURE_NET_PROXYXRT_FEATURE_NET不可变代理端点和凭据
XRT_FEATURE_NET_PROXY_HANDSHAKEProxy、Net Buffer传输无关的增量握手框架
XRT_FEATURE_NET_PROXY_SOCKS5Proxy HandshakeSOCKS5 CONNECT 和用户名密码认证
XRT_FEATURE_NET_PROXY_HTTP_CONNECTProxy Handshake、HTTP/1 Head、Base64HTTP CONNECT 和 Basic 认证
XRT_FEATURE_NET_PROXY_DIALProxy Handshake、TCP Dial托管 DNS、TCP 与代理握手的完整拨号生命周期

代理协议层不依赖 TCP、TLS、HTTP 客户端或 WebSocket。调用方可以把同一个握手对象接到 XRT TCP、自定义 Socket、测试传输或其他双向字节流。

代理对象

xnetproxyconfig 的所有视图只在 xrtNetProxyCreate 调用期间借用。创建成功后,xnetproxy 在一块精确分配中持有主机、用户名和密码的深拷贝,可以跨线程和请求共享。

XNET_PROXY_AUTH_AUTO 是默认策略:没有凭据时规范化为 NONE,存在凭据时规范化为 REQUIRED。SOCKS5 的 OPTIONAL 会同时提供匿名和用户名密码方法;HTTP CONNECT 的 REQUIRED 与 OPTIONAL 都预先发送 Basic 字段,避免在同一 TCP 连接上隐式重放 CONNECT。HTTP Basic 用户名不能包含冒号,密码允许任意字节。

xrtNetProxyRetain 和 xrtNetProxyRelease 管理共享引用。最后一个引用释放前会清零整个对象分配,避免凭据留在堆内存中。

增量握手

xnetproxyhandshake 由一个传输执行上下文独占驱动。创建时会深拷贝目标主机、增加代理引用并立即生成首个协议报文。

状态驱动规则:

  • XNET_PROXY_HANDSHAKE_WRITE:用 xrtNetProxyHandshakeOutput 借用当前连续输出,真正写出的字节数交给 xrtNetProxyHandshakeSent。部分写入受支持。
  • XNET_PROXY_HANDSHAKE_READ:把网络数据追加到调用方自己的 xnetbuf,再调用 xrtNetProxyHandshakeStep。
  • XNET_PROXY_HANDSHAKE_READY:隧道已经建立。SOCKS5 可以读取 xrtNetProxyHandshakeBound;HTTP CONNECT 没有绑定端点,该函数返回 XERR_NOT_FOUND。握手只消费输入链中的协议回复前缀,同包到达的应用数据仍留在原输入链中。
  • XNET_PROXY_HANDSHAKE_ERROR:使用 xrtNetProxyHandshakeError 读取握手捕获的结构化错误,使用 xrtNetProxyHandshakeCode 读取已经收到的 SOCKS5 线路回复码或 HTTP 状态码。

xrtNetProxyHandshakeOutput 只借用握手对象当前输出的首段。没有待发送数据或参数无效时,它返回 false,并把非空的输出参数规范化为 { NULL, 0 },调用方不会误用上一次查询留下的借用指针。

握手输出可能包含用户名和密码。确认发送的输出前缀会在释放或返回缓冲池前清零;销毁握手也会清零尚未发送的输出。

SOCKS5 契约

当前 SOCKS5 后端实现 RFC 1928 CONNECT 和 RFC 1929 用户名密码认证:

  • 目标支持域名、IPv4 和 IPv6;数字地址直接使用对应线路类型。
  • 用户名和密码各自遵守 255 字节线路上限。
  • 回复支持任意分片和同包合并。
  • 绑定端点支持 IPv4、IPv6 和域名回复。
  • 标准失败码 1 到 8 保留在线路码中,并映射为 XNET_ERROR_PROXY_CONNECT。
  • ReceiveLimit 是硬限制;默认 64 KiB,足以覆盖 SOCKS5,并为后续 HTTP CONNECT Header 提供统一容量契约。

当前 SOCKS5 公共握手只实现 CONNECT。未来的 BIND 和 UDP ASSOCIATE 应作为独立命令能力加入,不会改变 CONNECT 的认证、缓冲和错误契约。

HTTP CONNECT 契约

HTTP CONNECT 后端使用 HTTP/1.1 authority-form 请求,并始终生成与目标一致的 Host 字段:

  • 域名和 IPv4 使用 host:port;IPv6 使用 [address]:port,作用域百分号在线路上编码为 %25。
  • 目标必须是纯主机或数字地址,不能传入 URL、用户信息、路径、查询或片段。
  • 存在凭据时发送 Proxy-Authorization: Basic ...;认证临时区、未发送输出和托管发送副本都会安全清零。
  • 最终任意 2xx 都建立隧道;最多接受 8 个非 101 的 1xx 中间响应,101 明确视为协议错误。
  • 407 映射为 XERR_PERMISSION/XNET_ERROR_PROXY_AUTH,其他拒绝状态映射为 XERR_IO/XNET_ERROR_PROXY_CONNECT,畸形响应保留 xrt.http1 原因链。
  • 响应头增量接收并受 ReceiveLimit 硬限制。只有找到完整空行后才拉直一次,不为字段表分配内存,也不保留代理响应字段。
  • xrtNetProxyHandshakeCode 返回最终 HTTP 状态码;HTTP CONNECT 不虚构 SOCKS5 风格的绑定端点。

托管代理拨号

xrtNetProxyDial 是常用路径:它先复用 TCP Dial 解析并连接代理端点,再在获胜 Stream 的所属 Worker 上驱动传输无关握手。应用只提供最终目标、Stream 事件和一个完成回调,不需要手工搬运握手字节。

Config.Transport 完整保留 TCP Dial 的地址族、候选竞速、Worker 亲和性、连接超时与 Stream 硬边界。Config.Timeout 是代理拨号的全过程上限,覆盖 DNS、TCP 和握手;零值关闭这一层总超时,但不会改写 Transport.Timeout。ReceiveLimit 是握手协议输入硬上限,并且必须不大于 TCP ReadLimit。

托管 Dial 状态与最终目标主机名使用单块拥有分配,不为目标字符串建立第二个堆节点。代理配置、TCP Dial、协议握手和最终 Stream 仍保持独立引用,因为它们具有不同终态和公开生命周期;组合层只消除相同所有权边界内的重复分配,不用对象拼接换取脆弱的隐式依赖。

通过参数和代理配置校验后,组合层会先取得一份 Engine 初始化租约,再分配 Dial、安装全过程 Timer 并创建底层 TCP Dial。只有 Timer、TCP Dial 或最终 Stream 已经接管 Engine 生命周期后,这份临时租约才会释放;任一初始化步骤失败则在返回前回滚。初始化正在其他线程执行时,xrtNetEngineDestroy() 会以 XERR_STATE 拒绝销毁并保持 Engine 为 RUNNING,不会留下一个尚未返回、却已经引用失效 Engine 的半成品组合对象。

终态回调只在代理传输所属 Worker 发布一次,并且不会从 xrtNetProxyDial 的调用栈直接重入。从其他线程提交时,Worker 仍可以与提交 线程并发执行,回调可能早于调用方保存返回值;必须使用回调参数中的 pDial 识别操作。该参数与错误都只在回调期间借用,跨回调保留 Dial 时先调用 xrtNetProxyDialRef。

终态规则:

  • 成功时先安装最终用户事件,再按 Open、已预读 Read、完成回调的顺序发布。完成回调接管一个已经建立隧道的 xnetstream* 引用。
  • 失败、超时和取消时 Stream 参数为空,错误参数只在回调期间借用;Dial 对象保留完整错误原因链,可用 xrtNetProxyDialError 继续查询。
  • xrtNetProxyDialDestroy 只释放调用方持有的 Dial 引用,不等同于取消。放弃未完成操作时先调用 xrtNetProxyDialCancel,再释放引用。

xrtNetProxyDial 的非空返回值包含一份调用方引用。借用的回调参数不是额外 引用,不能无条件销毁;只有调用方已经同步取得返回引用,或者回调先显式增加 引用后,才能在对应所有权路径释放 Dial。

xrtNetProxyDialCancel 可从任意线程调用并且只允许第一次成功。取消会协作终止当前 DNS、候选 TCP 或握手 Stream;所有 Stream 操作仍被投递到其所属 Worker,不会从取消线程直接访问 Worker 私有缓冲。总超时复用同一取消路径,但终态规范化为 XNET_RESULT_TIMEOUT 和 XERR_TIMEOUT。

握手输出按 TCP WriteLimit 的剩余预算分段提交。短写、高低水位和同步 LowWater/Drain 重入都由组合层串行折叠;认证输出的托管副本在离开发送队列时安全清零。只有完成代理回复所需的字节会被消费,同包到达的应用数据仍留在 Stream 读缓冲,并在最终 Read 中原样交付。

xrtNetProxyDialState 区分 RESOLVING、CONNECTING、HANDSHAKE 和三个终态;xrtNetProxyDialStats 同时返回代理阶段与底层 TCP Dial 候选统计。DNS、TCP 和代理协议错误保留原始 cause,外层统一使用 xrt.net 代理错误码表达失败阶段。OOM 时如果连外层组合错误也无法分配,顶层可以直接是静态 XERR_MEMORY;调用方应使用 xrtErrorIs(error, XERR_MEMORY) 检查整条原因链。

托管路径同时支持已经编译进依赖闭包的 SOCKS5 CONNECT 和 HTTP CONNECT。只编译其中一个协议时,另一个类型会明确返回 XERR_UNSUPPORTED;XRT_FEATURE_NET_PROXY_DIAL 本身不强制携带任何具体代理协议,保持裁剪边界清晰。

需要端到端 TLS 时,启用 XRT_FEATURE_TLS_STREAM_DIAL_PROXY 并调用 xrtTlsDialProxy();该模块闭包显式包含 TLS Stream Dial 与 Proxy Dial。该入口 复用本节的代理拨号器建立隧道,但由外层 TLS Dial 统一拥有全过程期限、取消终态、错误链和传输统计。xtlsdialconfig 不嵌入代理 指针;直连 xrtTlsDial() 与代理 xrtTlsDialProxy() 是两个显式入口,以保持配置 ABI 不随功能宏改变,并避免裁剪构建静默退化为直连。

托管代理层不识别端口后端。select、IOCP 与 io_uring 共用同一份 SOCKS5、HTTP CONNECT、并发取消、OOM 回收和单头拨号断言;后端测试入口只选择 xnetportkind。新增端口实现时,应先通过 TCP Dial 契约,再直接复用这些组合测试,不能在代理状态机中增加平台分支。

xtlsstreamconfig类型

两个超时都使用毫秒;零值显式关闭对应计时器。 AsyncBytesLimit 和 AsyncCountLimit 是未完成操作的独立硬边界, AsyncBatch 限制一次 Worker 轮转完成的操作数。

字段类型语义
HandshakeTimeoutuint64HandshakeTimeout
CloseTimeoutuint64CloseTimeout
AsyncBytesLimitsize_tAsyncBytesLimit
AsyncCountLimituint32AsyncCountLimit
AsyncBatchuint32AsyncBatch
typedef struct xtlsstreamconfig { int64 HandshakeTimeout; int64 CloseTimeout; size_t AsyncBytesLimit; uint32 AsyncCountLimit; uint32 AsyncBatch; } xtlsstreamconfig;

xtlsstreamstate类型

FAILED 保存 TLS 或传输根因;CLOSED 只表示完成认证关闭。

值语义
XTLS_STREAM_CONNECTING连接中
XTLS_STREAM_HANDSHAKE握手阶段
XTLS_STREAM_OPEN开放(握手完成)
XTLS_STREAM_CLOSING关闭中
XTLS_STREAM_CLOSED已关闭
XTLS_STREAM_FAILED已失败
typedef enum xtlsstreamstate { XTLS_STREAM_CONNECTING = 0, XTLS_STREAM_HANDSHAKE, XTLS_STREAM_OPEN, XTLS_STREAM_CLOSING, XTLS_STREAM_CLOSED, XTLS_STREAM_FAILED } xtlsstreamstate;

xtlsstreamwait类型

条件 Future 是水平条件;END 表示收到认证 close_notify, CLOSE 表示底层传输和 TLS 组合对象进入最终终态。

值语义
XTLS_STREAM_WAIT_OPEN等待开放
XTLS_STREAM_WAIT_READ读方向
XTLS_STREAM_WAIT_WRITE写方向
XTLS_STREAM_WAIT_DRAIN排空策略
XTLS_STREAM_WAIT_END等待关闭完成
XTLS_STREAM_WAIT_CLOSE等待关闭
typedef enum xtlsstreamwait { XTLS_STREAM_WAIT_OPEN = 0, XTLS_STREAM_WAIT_READ, XTLS_STREAM_WAIT_WRITE, XTLS_STREAM_WAIT_DRAIN, XTLS_STREAM_WAIT_END, XTLS_STREAM_WAIT_CLOSE } xtlsstreamwait;

xtlsdialstate类型

Dial 状态区分名称解析、TCP 连接和 TLS 握手三个可取消阶段。

值语义
XTLS_DIAL_RESOLVING解析中
XTLS_DIAL_CONNECTING连接中
XTLS_DIAL_HANDSHAKE握手阶段
XTLS_DIAL_CONNECTED已连接
XTLS_DIAL_FAILED已失败
XTLS_DIAL_CANCELLED已取消
typedef enum xtlsdialstate { XTLS_DIAL_RESOLVING = 0, XTLS_DIAL_CONNECTING, XTLS_DIAL_HANDSHAKE, XTLS_DIAL_CONNECTED, XTLS_DIAL_FAILED, XTLS_DIAL_CANCELLED } xtlsdialstate;

xtlsdialconfig类型

Timeout 覆盖 DNS、TCP 和 TLS 全过程;零值只保留各阶段超时。

字段类型语义
TransportxnetdialconfigTransport
Streamxtlsstreamconfig流选择
Timeoutint64超时(毫秒)
ServerNameFromHostboolServerNameFromHost
typedef struct xtlsdialconfig { xnetdialconfig Transport; xtlsstreamconfig Stream; int64 Timeout; bool ServerNameFromHost; } xtlsdialconfig;

xtlsstreamevents类型

全部回调都在底层 TCP Stream 所属 Worker 上串行执行。

字段类型语义
Openvoid (*Open)(xtlsstream* pStream, ptr pData)流开放(握手完成)
Readvoid (*Read)(xtlsstream* pStream, const xnetbuf* pBuffer, ptr pData)收到解密缓冲
Endvoid (*End)(xtlsstream* pStream, ptr pData)对端写关闭
Writablevoid (*Writable)(xtlsstream* pStream, ptr pData)发送预算可用
Drainvoid (*Drain)(xtlsstream* pStream, ptr pData)发送队列排空
Closevoid (*Close)(xtlsstream* pStream, xnetresult Result, const xerror* pError, ptr pData)流关闭(含错误)
typedef struct xtlsstreamevents { void(*Open)(xtlsstream* pStream, ptr pData); void (*Read)(xtlsstream* pStream, const xnetbuf* pBuffer, ptr pData); void (*End)(xtlsstream* pStream, ptr pData); void (*Writable)(xtlsstream* pStream, ptr pData); void (*Drain)(xtlsstream* pStream, ptr pData); void (*Close)(xtlsstream* pStream, xnetresult Result, const xerror* pError, ptr pData); /* 客户端恢复队列新增票据时发布边沿;未启用恢复实现时不会调用。 回调使用 xrtTlsClientTakeResume 接管一张或全部票据。 */ void (*Ticket)(xtlsstream* pStream, ptr pData)

xtlslistenerstate类型

Listener 只发布已经完成 TLS 握手的 Stream,关闭监听不会隐式关闭已发布连接。

值语义
XTLS_LISTENER_OPEN监听中
XTLS_LISTENER_CLOSING关闭中
XTLS_LISTENER_CLOSED已关闭
typedef enum xtlslistenerstate { XTLS_LISTENER_OPEN = 0, XTLS_LISTENER_CLOSING, XTLS_LISTENER_CLOSED } xtlslistenerstate;

xtlslistenerevents类型

Accept 在目标 Stream 的 Worker 上执行,返回 true 后接管一个 Stream 引用。 Error 只报告监听层错误;单连接握手失败通过 HandshakeError 独立报告。

字段类型语义
Acceptbool (*Accept)(xtlslistener* pListener, xtlsstream* pStream, ptr pData)新 TLS 流完成接受(返回是否保留)
HandshakeErrorvoid (*HandshakeError)(xtlslistener* pListener, const xerror* pError, ptr pData)接受后握手失败
Errorvoid (*Error)(xtlslistener* pListener, const xerror* pError, ptr pData)监听级错误
Closevoid (*Close)(xtlslistener* pListener, ptr pData)监听器关闭
typedef struct xtlslistenerevents { bool(*Accept)(xtlslistener* pListener, xtlsstream* pStream, ptr pData); void (*HandshakeError)(xtlslistener* pListener, const xerror* pError, ptr pData); void (*Error)(xtlslistener* pListener, const xerror* pError, ptr pData); void (*Close)(xtlslistener* pListener, ptr pData)

xtlslistenerconfig类型

Listen 负责 TCP 接入,Tls 和 Stream 负责每条连接的 TLS 会话与组合层限制。 AcceptQueueLimit 只限制完成握手但尚未被 pull/Future 消费的连接; HandshakeLimit 在分配 TLS 会话前硬性限制并发握手数。 初始化默认完成队列 1024 条、并发握手 128 条,均可显式调整。

字段类型语义
ListenxnetlistenconfigListen
TlsxtlsserverconfigTls
Streamxtlsstreamconfig流选择
AcceptQueueLimituint32AcceptQueueLimit
HandshakeLimituint32HandshakeLimit
typedef struct xtlslistenerconfig { xnetlistenconfig Listen; xtlsserverconfig Tls; xtlsstreamconfig Stream; uint32 AcceptQueueLimit; uint32 HandshakeLimit; } xtlslistenerconfig;

xtlslistenerstats类型

统计值均为并发快照,累计计数在关闭后仍可读取。

字段类型语义
Statextlslistenerstate状态
Handshakesuint64Handshakes
Accepteduint64Accepted
Rejecteduint64Rejected
HandshakeErrorsuint64HandshakeErrors
ActiveHandshakesuint32ActiveHandshakes
PeakHandshakesuint32PeakHandshakes
QueuedAcceptsuint32QueuedAccepts
PeakQueuedAcceptsuint32PeakQueuedAccepts
AcceptWaitersuint32AcceptWaiters
typedef struct xtlslistenerstats { xtlslistenerstate State; uint64 Handshakes; uint64 Accepted; uint64 Rejected; uint64 HandshakeErrors; uint32 ActiveHandshakes; uint32 PeakHandshakes; uint32 QueuedAccepts; uint32 PeakQueuedAccepts; uint32 AcceptWaiters; } xtlslistenerstats;

xtlsstream类型

公开句柄声明不随 TLS Stream 实现裁剪变化。 不透明句柄或别名;生命周期与所有权见各使用方 API 节。

typedef struct xtlsstream xtlsstream;

xtlslistener类型

TLS 监听器(不透明):在 TCP 监听器上完成 TLS 接受。 不透明句柄或别名;生命周期与所有权见各使用方 API 节。

typedef struct xtlslistener xtlslistener;

xtlsdial类型

托管 TLS 拨号对象(不透明):串联 TCP 拨号与 TLS 握手。 不透明句柄或别名;生命周期与所有权见各使用方 API 节。

typedef struct xtlsdial xtlsdial;

xtlsdialproc类型

成功回调接管 TLS Stream 引用;失败时 Stream 为空且 Error 只在回调期间借用。 回调类型;参数与返回语义见签名及各使用方 API 节。

typedef void(*xtlsdialproc)( xtlsdial* pDial, xnetresult Result, xtlsstream* pStream, const xerror* pError, ptr pData)
TLS 身份

xrtTlsDial函数

解析主机、竞争 TCP 地址并完成 TLS 握手;成功 Stream 引用转移给完成回调。

xtlsdial* xrtTlsDial(xnetengine* pEngine, xnetresolver* pResolver, cstr sHost, uint16 iPort, const xtlsclientconfig* pTls, const xtlsdialconfig* pConfig, const xtlsstreamevents* pStreamEvents, ptr pStreamData, xtlsdialproc pDone, ptr pDoneData)

参数

参数方向约束说明
pEngine输入非空网络 Engine
pResolver输入非空名称解析器
sHost输入—主机名
iPort输入—端口
pTls输入非空TLS 对象
pConfig输入—配置
pStreamEvents输入非空流事件表
pStreamData输入—流用户数据
pDone输入—完成回调
pDoneData输入—回调数据
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_UNSUPPORTED — 算法、版本或能力不受支持
  • XERR_MEMORY — 分配失败

范例已编译验证

dial · stream tour

	pDial = xrtTlsDial(
		pEngine,
		pResolver,
		sHost,
		(uint16)iPort,
		&TlsConfig,
		&DialConfig,
		&Events,
		&Example,
		exampleTlsDialDone,
		&Example

xrtTlsDialProxy函数

经代理 CONNECT 隧道连接目标并继续同一个受管 TLS 状态机。代理对象只在调用期间借用;提交成功后,组合拨号持有其自己的代理引用。

xtlsdial* xrtTlsDialProxy(xnetengine* pEngine, xnetresolver* pResolver, const xnetproxy* pProxy, cstr sHost, uint16 iPort, const xtlsclientconfig* pTls, const xtlsdialconfig* pConfig, const xtlsstreamevents* pStreamEvents, ptr pStreamData, xtlsdialproc pDone, ptr pDoneData)

参数

参数方向约束说明
pEngine输入非空网络 Engine
pResolver输入非空名称解析器
pProxy输入非空、调用期间借用代理配置
sHost输入非空CONNECT 目标主机;默认也用于 SNI 与证书名称
iPort输入非零CONNECT 目标端口
pTls输入允许空TLS 客户端配置
pConfig输入允许空TCP、TLS 与全过程超时配置
pStreamEvents输入允许空成功 TLS Stream 的事件表
pStreamData输入—Stream 用户数据
pDone输入非空唯一终态完成回调
pDoneData输入—完成回调数据
返回值
返回含义失败时状态
非空TLS Dial 调用方引用—
NULL参数、配置、分配或提交失败见当前线程错误
错误
  • XERR_ARGUMENT — Engine、Resolver、Proxy、目标或完成回调非法
  • XERR_RANGE — Stream 或代理握手硬上限与传输配置冲突
  • XERR_UNSUPPORTED — 代理类型对应的握手协议未编译
  • XERR_CLOSED — Engine 已停止接受新对象
  • XERR_MEMORY — 分配失败

范例已编译验证

TLS 拨号 · 代理拨号 该入口仅在启用 XRT_FEATURE_TLS_STREAM_DIAL_PROXY 时声明;该模块闭包会显式 启用 TLS Stream Dial 与 Proxy Dial。xtlsdialconfig 不包含条件式代理字段, 因此其公开布局不随代理功能宏改变;直连与代理拨号也不会因为裁剪组合而静默互换。

pDial = xrtTlsDialProxy(
	pEngine, pResolver, pProxy, sHost, iPort,
	&TlsConfig, &DialConfig, &Events, pStreamData,
	onTlsDialDone, pDoneData
);

xrtTlsDialAsync函数

以 Future 接收完成握手的 TLS Stream;Open 先于成功终态发布。Future 持有一个 Stream 引用,取消请求协作终止 DNS、TCP 或 TLS 当前阶段。

xfuture* xrtTlsDialAsync(xnetengine* pEngine, xnetresolver* pResolver, cstr sHost, uint16 iPort, const xtlsclientconfig* pTls, const xtlsdialconfig* pConfig, const xtlsstreamevents* pStreamEvents, ptr pStreamData)

参数

参数方向约束说明
pEngine输入非空网络 Engine
pResolver输入非空名称解析器
sHost输入—主机名
iPort输入—端口
pTls输入非空TLS 对象
pConfig输入—配置
pStreamEvents输入非空流事件表
pStreamData输入—流用户数据
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_MEMORY — 分配失败

范例已编译验证

dial_future · stream tour

	pFuture = xrtTlsDialAsync(
		pEngine,
		pResolver,
		sHost,
		(uint16)iPort,
		&TlsConfig,
		&DialConfig,
		NULL,
		NULL
	);

xrtTlsDialCancel函数

原子受理取消;返回真保证最终结果不会再变为成功。

bool xrtTlsDialCancel(xtlsdial* pDial)

参数

参数方向约束说明
pDial输入非空托管拨号对象
返回值
返回含义失败时状态
true成功—
false失败见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_PROTOCOL(TLS 语义) — DER 或消息结构非法

范例已编译验证

listener_tour · stream tour

			bool bCancelled = xrtTlsDialCancel(pMidair);

xrtTlsDialConfigInit函数

初始化 TCP 拨号、TLS Stream 和总超时策略。

void xrtTlsDialConfigInit(xtlsdialconfig* pConfig)

参数

参数方向约束说明
pConfig输入—配置
返回值
返回含义失败时状态
无——
错误
  • 无 — 初始化不失败

范例已编译验证

dial · stream tour

	xrtTlsDialConfigInit(&DialConfig);

xrtTlsDialDestroy函数

释放 TLS Dial 引用;空指针视为空操作。

void xrtTlsDialDestroy(xtlsdial* pDial)

参数

参数方向约束说明
pDial输入非空托管拨号对象
返回值
返回含义失败时状态
无——
错误
  • 无 — 释放或重置不失败(Reset 系列见参数约束)

范例已编译验证

dial · stream tour

	xrtTlsDialDestroy(pDial);

xrtTlsDialError函数

失败或取消后借用完整错误原因链。

const xerror* xrtTlsDialError(const xtlsdial* pDial)

参数

参数方向约束说明
pDial输入非空托管拨号对象
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_MEMORY — 分配失败

范例已编译验证

listener_tour · stream tour

		xrtTlsDialError(pDial) == NULL ? "(none)" : "err");

xrtTlsDialRef函数

增加 TLS Dial 引用并返回原指针。

xtlsdial* xrtTlsDialRef(xtlsdial* pDial)

参数

参数方向约束说明
pDial输入非空托管拨号对象
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法

范例已编译验证

listener_tour · stream tour

		(xrtTlsDialRef(pDial) != pDial) ) {

xrtTlsDialState函数

返回当前拨号阶段或不可变终态。

xtlsdialstate xrtTlsDialState(const xtlsdial* pDial)

参数

参数方向约束说明
pDial输入非空托管拨号对象
返回值
返回含义失败时状态
枚举值当前取值—
错误
  • 无 — 纯查询,不设置错误

范例已编译验证

listener_tour · stream tour

	pSlot->DialState = xrtTlsDialState(pDial);

xrtTlsDialTransportStats函数

取得底层 TCP Dial 统计;TLS 握手阶段仍保留获胜地址信息。

bool xrtTlsDialTransportStats(const xtlsdial* pDial, xnetdialstats* pStats)

参数

参数方向约束说明
pDial输入非空托管拨号对象
pStats输入非空接收统计快照
返回值
返回含义失败时状态
true成功—
false失败见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_PROTOCOL(TLS 语义) — DER 或消息结构非法

范例已编译验证

listener_tour · stream tour

	if ( !xrtTlsDialTransportStats(pDial, &TransportStats) ||
		(TransportStats.AttemptsStarted < 1u) ) {

xrtTlsListenerAccept函数

pull 模式下非阻塞取得一个已完成握手的 Stream;空队列返回空指针。

xtlsstream* xrtTlsListenerAccept(xtlslistener* pListener)

参数

参数方向约束说明
pListener输入非空TLS 监听器
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_MEMORY — 分配失败

范例已编译验证

listener_tour · stream tour

		pServerA = xrtTlsListenerAccept(pListener);

xrtTlsListenerAcceptAsync函数

pull 模式下异步接受一个已完成握手的 Stream;Future 持有结果引用。

xfuture* xrtTlsListenerAcceptAsync(xtlslistener* pListener)

参数

参数方向约束说明
pListener输入非空TLS 监听器
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_MEMORY — 分配失败

范例已编译验证

listener_tour · stream tour

	pAcceptFuture = xrtTlsListenerAcceptAsync(pListener);

xrtTlsListenerAcceptWait函数

阻塞接受一个已完成握手的 Stream;禁止从该 Engine 的 Worker 调用。

xtlsstream* xrtTlsListenerAcceptWait(xtlslistener* pListener, int64 iTimeout, xcancel* pCancel)

参数

参数方向约束说明
pListener输入非空TLS 监听器
iTimeout输入—单调截止时间
pCancel输入—取消令牌
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_MEMORY — 分配失败

范例已编译验证

listener_tour · stream tour

	pServerC = xrtTlsListenerAcceptWait(pListener,
		EXAMPLE_DEADLINE_MS, NULL);

xrtTlsListenerClose函数

原子停止接入并丢弃尚未交付的连接;已交付连接保持独立生命周期。

bool xrtTlsListenerClose(xtlslistener* pListener)

参数

参数方向约束说明
pListener输入非空TLS 监听器
返回值
返回含义失败时状态
true成功—
false失败见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_PROTOCOL(TLS 语义) — DER 或消息结构非法

范例已编译验证

listener_tour · stream tour

		(void)xrtTlsListenerClose(pListener);

xrtTlsListenerConfigInit函数

初始化单 IPv4 动态端口、有界握手与有界完成队列。

void xrtTlsListenerConfigInit(xtlslistenerconfig* pConfig)

参数

参数方向约束说明
pConfig输入—配置
返回值
返回含义失败时状态
无——
错误
  • 无 — 初始化不失败

范例已编译验证

listener_tour · stream tour

	xrtTlsListenerConfigInit(&ListenerConfig);

xrtTlsListenerData函数

返回创建时保存的用户数据快照。

ptr xrtTlsListenerData(const xtlslistener* pListener)

参数

参数方向约束说明
pListener输入非空TLS 监听器
返回值
返回含义失败时状态
值当前取值—
错误
  • XERR_ARGUMENT — 指针为空或参数非法

范例已编译验证

listener_tour · stream tour

		(xrtTlsListenerData(pListener) != &EngineConfig) ) {

xrtTlsListenerDestroy函数

释放 Listener 引用;不会隐式关闭仍在监听的对象。

void xrtTlsListenerDestroy(xtlslistener* pListener)

参数

参数方向约束说明
pListener输入非空TLS 监听器
返回值
返回含义失败时状态
无——
错误
  • 无 — 释放或重置不失败(Reset 系列见参数约束)

范例已编译验证

listener_tour · stream tour

	xrtTlsListenerDestroy(pListenerRef);

xrtTlsListenerLocal函数

复制监听 Socket 的实际本地地址,支持动态端口。

bool xrtTlsListenerLocal(xtlslistener* pListener, xnetaddr* pAddress)

参数

参数方向约束说明
pListener输入非空TLS 监听器
pAddress输入非空接收地址
返回值
返回含义失败时状态
true成功—
false失败见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_PROTOCOL(TLS 语义) — DER 或消息结构非法

范例已编译验证

listener_tour · stream tour

		!xrtTlsListenerLocal(pListener, &Address) ||

xrtTlsListenerRef函数

增加 Listener 引用并返回原指针。

xtlslistener* xrtTlsListenerRef(xtlslistener* pListener)

参数

参数方向约束说明
pListener输入非空TLS 监听器
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法

范例已编译验证

listener_tour · stream tour

	pListenerRef = xrtTlsListenerRef(pListener);

xrtTlsListenerStart函数

同步完成 TCP 绑定并开始异步接入;配置数组只在调用期间借用。Listener 会保留 Context、Identity,并深复制 ALPN 协议列表。SelectContext 与 ResumeContext 由调用方持有,必须存活到 Listener 关闭回调结束。

xtlslistener* xrtTlsListenerStart(xnetengine* pEngine, const xtlslistenerconfig* pConfig, const xtlslistenerevents* pEvents, const xtlsstreamevents* pStreamEvents, ptr pData)

参数

参数方向约束说明
pEngine输入非空网络 Engine
pConfig输入—配置
pEvents输入—事件表
pStreamEvents输入非空流事件表
pData输入—用户数据
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_MEMORY — 分配失败

范例已编译验证

listener_tour · stream tour

	pListener = xrtTlsListenerStart(pEngine, &ListenerConfig, NULL,
		NULL, &EngineConfig);

xrtTlsListenerState函数

返回 Listener 当前生命周期状态。

xtlslistenerstate xrtTlsListenerState(const xtlslistener* pListener)

参数

参数方向约束说明
pListener输入非空TLS 监听器
返回值
返回含义失败时状态
枚举值当前取值—
错误
  • 无 — 纯查询,不设置错误

范例已编译验证

listener_tour · stream tour

		(xrtTlsListenerState(pListener) != XTLS_LISTENER_OPEN) ||

xrtTlsListenerStats函数

复制 Listener 的并发统计快照。

bool xrtTlsListenerStats(const xtlslistener* pListener, xtlslistenerstats* pStats)

参数

参数方向约束说明
pListener输入非空TLS 监听器
pStats输入非空接收统计快照
返回值
返回含义失败时状态
true成功—
false失败见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_PROTOCOL(TLS 语义) — DER 或消息结构非法

范例已编译验证

listener_tour · stream tour

	if ( !xrtTlsListenerStats(pListener, &ListenerStats) ||
		(ListenerStats.Accepted < 3u) ||
		(ListenerStats.Handshakes < 3u) ) {

xrtTlsStreamAbort函数

从任意线程立即放弃 TLS 与 TCP 会话。失败收尾尚未完成时仍会中止 TCP,但不会覆盖已经保存的首个根因。

bool xrtTlsStreamAbort(xtlsstream* pStream)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
返回值
返回含义失败时状态
true成功—
false失败见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_PROTOCOL(TLS 语义) — DER 或消息结构非法

范例已编译验证

dial · stream tour

			(void)xrtTlsStreamAbort(pStream);

xrtTlsStreamAccept函数

在 TCP Accept 回调内接管 Stream;返回值应直接作为该回调结果。

bool xrtTlsStreamAccept(xnetstream* pTransport, const xtlsserverconfig* pTls, const xtlsstreamconfig* pConfig, const xtlsstreamevents* pEvents, ptr pData, xtlsstream** ppStream)

参数

参数方向约束说明
pTransport输入非空传输 TCP 流
pTls输入非空TLS 对象
pConfig输入—配置
pEvents输入—事件表
pData输入—用户数据
ppStream输入非空接收流对象
返回值
返回含义失败时状态
true成功—
false失败见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_STATE — 状态非法(顺序、已关闭、非所属线程)
  • XERR_PROTOCOL — 接受的字节不是有效 TLS 记录
  • XERR_MEMORY — 分配失败

范例已编译验证

stream · stream tour

	bAccepted = xrtTlsStreamAccept(
		pTransport,
		&pExample->ServerConfig,
		&pExample->StreamConfig,
		&pExample->StreamEvents,
		pExample,
		&pStream
	);

xrtTlsStreamAsyncBytes函数

返回尚未由所属 Worker 终结的异步发送负载字节数。

size_t xrtTlsStreamAsyncBytes(const xtlsstream* pStream)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
返回值
返回含义失败时状态
>= 0数量或字节数—
错误
  • XERR_ARGUMENT — 指针为空或参数非法

范例已编译验证

stream_tour · stream tour

	(void)xrtTlsStreamAsyncBytes(pStream);

xrtTlsStreamAsyncCount函数

返回异步发送、接收和条件等待的合计操作数。

uint32 xrtTlsStreamAsyncCount(const xtlsstream* pStream)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
返回值
返回含义失败时状态
数值当前取值—
错误
  • 无 — 纯查询,不设置错误

范例已编译验证

stream_tour · stream tour

	(void)xrtTlsStreamAsyncCount(pStream);

xrtTlsStreamAttach函数

在已公开的 TCP Stream 所属 Worker 上接管 Transport 和 Session。Transport 必须仍可双向收发,调用方必须停止直接操作其 IO。成功时接管两者的调用方引用;失败时所有权、Session 分配归属和 Transport 事件均保持不变,输出清空。

bool xrtTlsStreamAttach(xnetstream* pTransport, xtlssession* pSession, const xtlsstreamconfig* pConfig, const xtlsstreamevents* pEvents, ptr pData, xtlsstream** ppStream)

参数

参数方向约束说明
pTransport输入非空传输 TCP 流
pSession输入/输出非空TLS 会话
pConfig输入—配置
pEvents输入—事件表
pData输入—用户数据
ppStream输入非空接收流对象
返回值
返回含义失败时状态
true成功—
false失败见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_PROTOCOL(TLS 语义) — DER 或消息结构非法

范例已编译验证

stream_tour · stream tour

		pTask->bOk = xrtTlsStreamAttach(pTask->pTcp,
			pTask->pSession, pTask->pStream, pTask->pEvents,
			pTask->pData, &pTask->pTls);

xrtTlsStreamAvailable函数

返回当前待应用消费明文字节数的并发快照。

size_t xrtTlsStreamAvailable(const xtlsstream* pStream)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
返回值
返回含义失败时状态
>= 0数量或字节数—
错误
  • 无 — 纯查询,不设置错误

范例已编译验证

dial · stream tour

	while ( xrtTlsStreamAvailable(pStream) != 0 ) {

xrtTlsStreamBuffer函数

在所属 Worker 上借用明文块链,借用期不超过本次回调。默认在当前明文消费前暂停底层读取;增量协议解析器可显式请求 ReadMore。

const xnetbuf* xrtTlsStreamBuffer(xtlsstream* pStream)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_MEMORY — 分配失败

范例已编译验证

dial · stream tour

		const xnetbuf* pPlain = xrtTlsStreamBuffer(pStream);

xrtTlsStreamClient函数

在已连接 TCP Stream 上创建 TLS 客户端。适用于代理隧道、STARTTLS 和自定义拨号;成功时接管 Transport 引用。

bool xrtTlsStreamClient(xnetstream* pTransport, const xtlsclientconfig* pTls, const xtlsstreamconfig* pConfig, const xtlsstreamevents* pEvents, ptr pData, xtlsstream** ppStream)

参数

参数方向约束说明
pTransport输入非空传输 TCP 流
pTls输入非空TLS 对象
pConfig输入—配置
pEvents输入—事件表
pData输入—用户数据
ppStream输入非空接收流对象
返回值
返回含义失败时状态
true成功—
false失败见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_PROTOCOL(TLS 语义) — DER 或消息结构非法

范例已编译验证

stream_tour · stream tour

		pTask->bOk = xrtTlsStreamClient(pTask->pTcp,
			pTask->pClient, pTask->pStream, pTask->pEvents,
			pTask->pData, &pTask->pTls);

xrtTlsStreamClose函数

从任意线程请求 close_notify、等待对端认证关闭并排空 TCP。调用前已接纳的异步发送会先按 FIFO 完成;调用后的新发送不再接纳。

bool xrtTlsStreamClose(xtlsstream* pStream)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
返回值
返回含义失败时状态
true成功—
false失败见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_PROTOCOL(TLS 语义) — DER 或消息结构非法

范例已编译验证

dial · stream tour

		if ( !xrtTlsStreamClose(pStream) ) {

xrtTlsStreamConfigInit函数

初始化握手与认证关闭超时。

void xrtTlsStreamConfigInit(xtlsstreamconfig* pConfig)

参数

参数方向约束说明
pConfig输入—配置
返回值
返回含义失败时状态
无——
错误
  • 无 — 初始化不失败

范例已编译验证

stream · stream tour

	xrtTlsStreamConfigInit(&Example.StreamConfig);

xrtTlsStreamConnect函数

创建 TLS 客户端并异步连接数字 TCP 地址。

xtlsstream* xrtTlsStreamConnect(xnetengine* pEngine, const xnetaddr* pRemote, uint64 iAffinity, const xnetstreamconfig* pTransport, const xtlsclientconfig* pTls, const xtlsstreamconfig* pConfig, const xtlsstreamevents* pEvents, ptr pData)

参数

参数方向约束说明
pEngine输入非空网络 Engine
pRemote输入非空远端地址
iAffinity输入—亲和 Worker 标识
pTransport输入非空传输 TCP 流
pTls输入非空TLS 对象
pConfig输入—配置
pEvents输入—事件表
pData输入—用户数据
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_STATE — 状态非法(顺序、已关闭、非所属线程)
  • xrt.net 域错误 — TCP 连接提交失败
  • XERR_MEMORY — 分配失败

范例已编译验证

listener_tour · stream tour

	pClientB = xrtTlsStreamConnect(pEngine, &Address, 0, NULL,
		&ClientConfigB, NULL, &ClientEvents, &ClientB);

xrtTlsStreamConsume函数

在所属 Worker 上安全消费精确数量的明文。

bool xrtTlsStreamConsume(xtlsstream* pStream, size_t iSize)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
iSize输入—字节数
返回值
返回含义失败时状态
true成功—
false失败见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_PROTOCOL(TLS 语义) — DER 或消息结构非法

范例已编译验证

dial · stream tour

		if ( !xrtTlsStreamConsume(pStream, Span.Size) ) {

xrtTlsStreamData函数

返回线程安全的用户数据指针快照,不延长目标生命周期。

ptr xrtTlsStreamData(const xtlsstream* pStream)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
返回值
返回含义失败时状态
值当前取值—
错误
  • XERR_ARGUMENT — 指针为空或参数非法

范例已编译验证

stream_tour · stream tour

	pClient->bDataOk = xrtTlsStreamData(pStream) == pClient;

xrtTlsStreamDestroy函数

释放 TLS Stream 引用;关闭必须另行请求。

void xrtTlsStreamDestroy(xtlsstream* pStream)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
返回值
返回含义失败时状态
无——
错误
  • 无 — 释放或重置不失败(Reset 系列见参数约束)

范例已编译验证

dial · stream tour

	xrtTlsStreamDestroy(Example.Stream);

xrtTlsStreamError函数

终态失败时借用保存的 TLS 或传输根因。

const xerror* xrtTlsStreamError(const xtlsstream* pStream)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_MEMORY — 分配失败

范例已编译验证

stream_tour · stream tour

		xrtTlsStreamError(pClientA) == NULL ? "(none)" : "err");

xrtTlsStreamPending函数

返回 TLS 密文暂存与底层 TCP 队列的总待发字节并发快照。

size_t xrtTlsStreamPending(const xtlsstream* pStream)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
返回值
返回含义失败时状态
>= 0数量或字节数—
错误
  • XERR_ARGUMENT — 指针为空或参数非法

范例已编译验证

stream_tour · stream tour

	while ( xrtTlsStreamPending(pClientB) != 0u ) {

xrtTlsStreamPullup函数

在所属 Worker 上把精确明文前缀按需连续化并返回借用视图。不消费明文;视图在下一次明文缓冲修改或消费前有效,零长度和越界请求失败。

bool xrtTlsStreamPullup(xtlsstream* pStream, size_t iSize, xnetspan* pSpan)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
iSize输入—字节数
pSpan输入非空接收分片
返回值
返回含义失败时状态
true成功—
false失败见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_PROTOCOL(TLS 语义) — DER 或消息结构非法

范例已编译验证

stream_tour · stream tour

		if ( !xrtTlsStreamPullup(pStream, 3u, &Span) ||
			(Span.Size < 3u) ) {

xrtTlsStreamRead函数

在所属 Worker 上复制并安全消费明文。

xtlsresult xrtTlsStreamRead(xtlsstream* pStream, void* pOutput, size_t iCapacity, size_t* pRead)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
pOutput输入非空输出缓冲
iCapacity输入—容量
pRead输入非空接收读取游标
返回值
返回含义失败时状态
XTLS_OK成功—
XTLS_AGAIN需要更多输入—
XTLS_CLOSED会话已关闭—
XTLS_ERROR失败—
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_STATE — 状态非法(顺序、已关闭、非所属线程)
  • XERR_PROTOCOL — 记录解密失败

范例已编译验证

stream_tour · stream tour

	(void)xrtTlsStreamRead(pStream, pClient->ReadOut, 4u,
		&pClient->iRead);

xrtTlsStreamReadMore函数

在 Read 回调保留现有明文时,请求继续解密并在明文增长后再次发布 Read。累积量受 Context PlainLimit 硬约束,并必须为一条最大明文 record 留出空间。普通消费者无需调用,重复请求是幂等的;请求待完成时不能替换事件接收者。

bool xrtTlsStreamReadMore(xtlsstream* pStream)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
返回值
返回含义失败时状态
true成功—
false失败见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_PROTOCOL(TLS 语义) — DER 或消息结构非法

范例已编译验证

stream_tour · stream tour

		pClient->bReadMore = xrtTlsStreamReadMore(pStream);

xrtTlsStreamRecvAsync函数

在拉取模式下复制并消费当前可用明文。零上限表示读取全部当前明文;成功值是由 Future 持有的 xnetbytes。

xfuture* xrtTlsStreamRecvAsync(xtlsstream* pStream, size_t iMaxBytes)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
iMaxBytes输入—最多字节数
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_MEMORY — 分配失败

范例已编译验证

stream_future · stream tour

	xfuture* pFuture = xrtTlsStreamRecvAsync(
		pStream,
		64u * 1024u
	);

xrtTlsStreamRef函数

增加 TLS Stream 引用并返回原指针;引用耗尽时返回空并设置状态错误。

xtlsstream* xrtTlsStreamRef(xtlsstream* pStream)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法

范例已编译验证

dial_future · stream tour

	pStream = xrtTlsStreamRef(
		(xtlsstream*)xrtFutureValue(pFuture)
	);

xrtTlsStreamSend函数

在所属 Worker 上把明文编码为记录;允许成功短写。

xtlsresult xrtTlsStreamSend(xtlsstream* pStream, const void* pData, size_t iSize, size_t* pWritten)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
pData输入—用户数据
iSize输入—字节数
pWritten输入非空接收写出数量
返回值
返回含义失败时状态
XTLS_OK成功—
XTLS_AGAIN需要更多输入—
XTLS_CLOSED会话已关闭—
XTLS_ERROR失败—
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_RANGE — 发送预算已满
  • XERR_STATE — 握手未完成或已关闭
  • XERR_PROTOCOL — 记录保护失败

范例已编译验证

dial · stream tour

		xtlsresult Result = xrtTlsStreamSend(
			pStream,
			pExample->Request + pExample->Sent,
			pExample->RequestSize - pExample->Sent,
			&iWritten
		);

xrtTlsStreamSendAsync函数

从任意线程复制并按 FIFO 提交一段完整明文。Future 在全部明文被 TLS 会话受理时完成;排空必须另行等待 DRAIN。取消只在首个字节受理前有效,已开始的发送保持完整和有序。Close 线性化前已接纳的发送保证先完成,之后的发送以 STATE 拒绝。

xfuture* xrtTlsStreamSendAsync(xtlsstream* pStream, const void* pData, size_t iSize)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
pData输入—用户数据
iSize输入—字节数
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_MEMORY — 分配失败

范例已编译验证

stream_future · stream tour

	if ( !exampleTlsFutureResolved(xrtTlsStreamSendAsync(
		pStream,
		pData,
		iSize
	)) ) {

xrtTlsStreamSendBound函数

在所属 Worker 上返回一次明文发送产生的精确密文线路字节数。结果包含记录头、显式 nonce、内层类型和认证标签,失败不修改 pBound。pBound 不得与 Stream 或其 Session 对象存储重叠。

bool xrtTlsStreamSendBound(xtlsstream* pStream, size_t iPlainSize, size_t* pBound)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
iPlainSize输入—明文长度
pBound输入非空接收输出上界
返回值
返回含义失败时状态
true成功—
false失败见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_PROTOCOL(TLS 语义) — DER 或消息结构非法

范例已编译验证

stream_tour · stream tour

	(void)xrtTlsStreamSendBound(pStream, 64u, &pClient->iBound);

xrtTlsStreamSendVec函数

在所属 Worker 上依次编码明文片段;返回跨片段的连续受理前缀。

xtlsresult xrtTlsStreamSendVec(xtlsstream* pStream, const xnetspan* pSpans, size_t iCount, size_t* pWritten)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
pSpans输入非空分片数组
iCount输入—数量
pWritten输入非空接收写出数量
返回值
返回含义失败时状态
XTLS_OK成功—
XTLS_AGAIN需要更多输入—
XTLS_CLOSED会话已关闭—
XTLS_ERROR失败—
错误
  • XERR_ARGUMENT — 指针为空或参数非法

范例已编译验证

stream_tour · stream tour

	(void)xrtTlsStreamSendVec(pStream, Vec, 2, &iWritten);

xrtTlsStreamSendVecAsync函数

从任意线程复制片段并按 FIFO 提交为一段连续明文。全部片段在返回前完成校验和复制,失败不会发布部分操作。

xfuture* xrtTlsStreamSendVecAsync(xtlsstream* pStream, const xnetspan* pSpans, size_t iCount)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
pSpans输入非空分片数组
iCount输入—数量
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_MEMORY — 分配失败

范例已编译验证

stream_tour · stream tour

	pSendFuture = xrtTlsStreamSendVecAsync(pClientB, AsyncVec, 2);

xrtTlsStreamSession函数

在所属 Worker 上借用协议会话,供 ALPN、票据等高级查询。

xtlssession* xrtTlsStreamSession(xtlsstream* pStream)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_MEMORY — 分配失败

范例已编译验证

stream_tour · stream tour

	pClient->bSessionOk = xrtTlsStreamSession(pStream) != NULL;

xrtTlsStreamSetEvents函数

在所属 Worker 上替换已打开 TLS Stream 的事件与用户数据。不会自动重放当前明文缓冲,协议升级层必须显式处理已有后缀。

bool xrtTlsStreamSetEvents(xtlsstream* pStream, const xtlsstreamevents* pEvents, ptr pData)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
pEvents输入—事件表
pData输入—用户数据
返回值
返回含义失败时状态
true成功—
false失败见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_PROTOCOL(TLS 语义) — DER 或消息结构非法

范例已编译验证

stream_tour · stream tour

	pTask->bOk = xrtTlsStreamSetEvents(pTask->pStream,
		pTask->pEvents, NULL);

xrtTlsStreamState函数

返回组合 Stream 状态的并发快照。

xtlsstreamstate xrtTlsStreamState(const xtlsstream* pStream)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
返回值
返回含义失败时状态
枚举值当前取值—
错误
  • 无 — 纯查询,不设置错误

范例已编译验证

dial_future · stream tour

	while ( (xrtTlsStreamState(pStream) != XTLS_STREAM_CLOSED) &&
		(xrtTlsStreamState(pStream) != XTLS_STREAM_FAILED) ) {

xrtTlsStreamTransport函数

借用底层 TCP Stream,调用方不得改变其 IO 状态机。

xnetstream* xrtTlsStreamTransport(const xtlsstream* pStream)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_MEMORY — 分配失败

范例已编译验证

listener_tour · stream tour

		xrtNetStreamWorker(xrtTlsStreamTransport(pServerA)),

xrtTlsStreamWaitAsync函数

建立 OPEN、READ、WRITE、DRAIN、END 或 CLOSE 条件 Future。WRITE 要求发送 FIFO 清空且当前至少可受理明文;DRAIN 还要求 TLS 与 TCP 两级发送队列归零。END 在已认证明文全部交付后完成。取消只移除本次等待。

xfuture* xrtTlsStreamWaitAsync(xtlsstream* pStream, xtlsstreamwait Wait)

参数

参数方向约束说明
pStream输入/输出非空TLS 组合流
Wait输入非空等待条件
返回值
返回含义失败时状态
非空对象或借用—
NULL失败或不适用见错误
错误
  • XERR_ARGUMENT — 指针为空或参数非法
  • XERR_MEMORY — 分配失败

范例已编译验证

stream_future · stream tour

	return exampleTlsFutureResolved(xrtTlsStreamWaitAsync(
		pStream,
		XTLS_STREAM_WAIT_DRAIN
	));

常量索引常量

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

常量说明
XTLS_DIAL_CANCELLED见头文件注释
XTLS_DIAL_CONNECTED见头文件注释
XTLS_DIAL_CONNECTING见头文件注释
XTLS_DIAL_FAILED见头文件注释
XTLS_DIAL_HANDSHAKE见头文件注释
XTLS_DIAL_RESOLVING见头文件注释
XTLS_LISTENER_CLOSED见头文件注释
XTLS_LISTENER_CLOSING见头文件注释
XTLS_LISTENER_OPEN见头文件注释
XTLS_STREAM_ASYNC_BATCH_DEFAULT见头文件注释
XTLS_STREAM_ASYNC_BYTES_DEFAULT见头文件注释
XTLS_STREAM_ASYNC_COUNT_DEFAULT见头文件注释
XTLS_STREAM_CLOSED见头文件注释
XTLS_STREAM_CLOSE_TIMEOUT_DEFAULT见头文件注释
XTLS_STREAM_CLOSING见头文件注释
XTLS_STREAM_CONNECTING见头文件注释
XTLS_STREAM_FAILED见头文件注释
XTLS_STREAM_HANDSHAKE见头文件注释
XTLS_STREAM_HANDSHAKE_TIMEOUT_DEFAULT见头文件注释
XTLS_STREAM_OPEN见头文件注释
XTLS_STREAM_WAIT_CLOSE见头文件注释
XTLS_STREAM_WAIT_DRAIN见头文件注释
XTLS_STREAM_WAIT_END见头文件注释
XTLS_STREAM_WAIT_OPEN见头文件注释
XTLS_STREAM_WAIT_READ见头文件注释
XTLS_STREAM_WAIT_WRITE见头文件注释