TLS 1.3 PSK 会话恢复: NewSessionTicket 签发/解析、ticket+binder 校验、selected_identity 回选、恢复握手免证书(pazessl -sess_in/-sess_out 端到端验证); 构建脚本输出 bin/ 并支持独立命令与 psftp; 修复 SFTP 二进制传输与 copy_id -P; 补充 tests/ 调试与 verify_tls
485 行
18 KiB
C
485 行
18 KiB
C
/* tls_conn.c —— PazeSSL 高层 API: 配置 + 连接对象生命周期 + 应用数据读写
|
|
*
|
|
* 同时包含 tls_client/tls_server 握手入口(版本协商:先尝试 1.3,失败则回退 1.2)。
|
|
* TLS 1.2 回退状态机在 tls_handshake12.c 中实现。
|
|
*/
|
|
#include "tls_internal.h"
|
|
#include "paze/encoding.h"
|
|
#include <time.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#ifndef strdup
|
|
#ifdef _WIN32
|
|
#define strdup _strdup
|
|
#endif
|
|
#endif
|
|
|
|
/* ============================================================
|
|
* 配置
|
|
* ============================================================ */
|
|
paze_tls_config_t *paze_tls_config_new(paze_tls_role_t role) {
|
|
paze_tls_config_t *c = (paze_tls_config_t *)calloc(1, sizeof(*c));
|
|
if (!c) return NULL;
|
|
c->role = role;
|
|
c->enable_1_3 = 1;
|
|
c->enable_1_2 = 1;
|
|
c->verify_mode = PAZE_TLS_VERIFY_REQUIRED;
|
|
return c;
|
|
}
|
|
|
|
void paze_tls_config_free(paze_tls_config_t *cfg) {
|
|
if (!cfg) return;
|
|
for (int i = 0; i < cfg->alpn_count; i++) free(cfg->alpn[i]);
|
|
if (cfg->cert_der) free(cfg->cert_der);
|
|
if (cfg->anchors) {
|
|
for (int i = 0; i < cfg->anchor_count; i++) paze_x509_free(&cfg->anchors[i]);
|
|
free(cfg->anchors);
|
|
}
|
|
if (cfg->anchors_owned) free(cfg->anchors_owned);
|
|
/* 私钥:rsa 大数字段由 calloc 内存清零后释放即可(不持 malloc) */
|
|
free(cfg);
|
|
}
|
|
|
|
paze_status_t paze_tls_config_set_sni(paze_tls_config_t *cfg, const char *host) {
|
|
if (!cfg || !host) return PAZE_ERR_INVALID;
|
|
size_t n = strlen(host);
|
|
if (n > PAZE_TLS_SNI_MAX) return PAZE_ERR_OVERFLOW;
|
|
memcpy(cfg->sni, host, n); cfg->sni[n] = '\0';
|
|
return PAZE_OK;
|
|
}
|
|
|
|
paze_status_t paze_tls_config_set_alpn(paze_tls_config_t *cfg, const char *const *alpn, int n) {
|
|
if (!cfg || !alpn || n <= 0 || n > PAZE_TLS_MAX_ALPN) return PAZE_ERR_INVALID;
|
|
for (int i = 0; i < n; i++) {
|
|
cfg->alpn[i] = strdup(alpn[i]);
|
|
if (!cfg->alpn[i]) return PAZE_ERR_NOMEM;
|
|
}
|
|
cfg->alpn_count = n;
|
|
return PAZE_OK;
|
|
}
|
|
|
|
/* 由证书 PEM 文本构造证书链(CertificateEntry 序列)。
|
|
* 支持多张证书串联:依次拼接 [3字节长度前缀 + DER]。
|
|
* 返回 malloc 的缓冲区,*outlen 为长度。 */
|
|
static paze_status_t build_cert_chain_from_pem(const char *cert_pem,
|
|
uint8_t **out, size_t *outlen) {
|
|
/* 统计证书数量 */
|
|
int count = 0;
|
|
const char *p = cert_pem;
|
|
while ((p = strstr(p, "-----BEGIN CERTIFICATE-----")) != NULL) {
|
|
count++; p++;
|
|
}
|
|
if (count == 0) return PAZE_ERR_FORMAT;
|
|
|
|
/* 分配缓冲区:每张证书 3 + DER 长度 */
|
|
size_t alloc = 16384;
|
|
uint8_t *buf = (uint8_t *)malloc(alloc);
|
|
if (!buf) return PAZE_ERR_NOMEM;
|
|
size_t total = 0;
|
|
|
|
/* 逐张解析 */
|
|
const char *rest = cert_pem;
|
|
for (int i = 0; i < count; i++) {
|
|
const char *b = strstr(rest, "-----BEGIN CERTIFICATE-----");
|
|
if (!b) break;
|
|
const char *e = strstr(b, "-----END CERTIFICATE-----");
|
|
if (!e) break;
|
|
e += strlen("-----END CERTIFICATE-----");
|
|
|
|
/* 提取本张 PEM 文本 */
|
|
size_t blklen = (size_t)(e - b);
|
|
char *blk = (char *)malloc(blklen + 1);
|
|
if (!blk) { free(buf); return PAZE_ERR_NOMEM; }
|
|
memcpy(blk, b, blklen);
|
|
blk[blklen] = '\0';
|
|
|
|
size_t dlen = 0;
|
|
uint8_t *der = paze_pem_decode(blk, "CERTIFICATE", &dlen);
|
|
free(blk);
|
|
if (!der) { free(buf); return PAZE_ERR_FORMAT; }
|
|
|
|
/* 扩容 */
|
|
if (total + 3 + dlen > alloc) {
|
|
alloc = (total + 3 + dlen) * 2;
|
|
uint8_t *nb = (uint8_t *)realloc(buf, alloc);
|
|
if (!nb) { free(der); free(buf); return PAZE_ERR_NOMEM; }
|
|
buf = nb;
|
|
}
|
|
|
|
buf[total] = (uint8_t)(dlen >> 16);
|
|
buf[total + 1] = (uint8_t)(dlen >> 8);
|
|
buf[total + 2] = (uint8_t)(dlen);
|
|
memcpy(buf + total + 3, der, dlen);
|
|
total += 3 + dlen;
|
|
free(der);
|
|
rest = e;
|
|
}
|
|
|
|
*out = buf; *outlen = total;
|
|
return PAZE_OK;
|
|
}
|
|
|
|
paze_status_t paze_tls_config_set_cert_chain(paze_tls_config_t *cfg,
|
|
const char *cert_pem,
|
|
const char *privkey_pem) {
|
|
if (!cfg || !cert_pem || !privkey_pem) return PAZE_ERR_INVALID;
|
|
uint8_t *der = NULL; size_t dlen = 0;
|
|
paze_status_t st = build_cert_chain_from_pem(cert_pem, &der, &dlen);
|
|
if (st != PAZE_OK) return st;
|
|
free(cfg->cert_der);
|
|
cfg->cert_der = der; cfg->cert_der_len = dlen;
|
|
|
|
/* 解析私钥:支持 RSA PKCS#1 和 ECDSA P-256 */
|
|
if (strstr(privkey_pem, "RSA PRIVATE KEY")) {
|
|
cfg->privkey.kind = PAZE_X509_PK_RSA;
|
|
return paze_rsa_priv_from_pem(&cfg->privkey.rsa, privkey_pem);
|
|
}
|
|
if (strstr(privkey_pem, "EC PRIVATE KEY")) {
|
|
cfg->privkey.kind = PAZE_X509_PK_ECDSA_P256;
|
|
paze_ecdsa_priv_t ek;
|
|
paze_status_t ps = paze_ecdsa_priv_from_pem(&ek, privkey_pem);
|
|
if (ps != PAZE_OK) return ps;
|
|
memcpy(cfg->privkey.ecdsa_d, ek.d, 32);
|
|
return PAZE_OK;
|
|
}
|
|
if (strstr(privkey_pem, "PRIVATE KEY")) {
|
|
return PAZE_ERR_NOTSUP; /* PKCS#8 暂不支持 */
|
|
}
|
|
return PAZE_ERR_NOTSUP;
|
|
}
|
|
|
|
paze_status_t paze_tls_config_set_trust_roots(paze_tls_config_t *cfg,
|
|
const char *roots_pem) {
|
|
if (!cfg || !roots_pem) return PAZE_ERR_INVALID;
|
|
|
|
/* 统计证书数量 */
|
|
int count = 0;
|
|
const char *p = roots_pem;
|
|
while ((p = strstr(p, "-----BEGIN CERTIFICATE-----")) != NULL) {
|
|
count++; p++;
|
|
}
|
|
if (count == 0) return PAZE_ERR_FORMAT;
|
|
|
|
paze_x509_cert_t *roots = (paze_x509_cert_t *)calloc((size_t)count, sizeof(paze_x509_cert_t));
|
|
if (!roots) return PAZE_ERR_NOMEM;
|
|
|
|
const char *rest = roots_pem;
|
|
int loaded = 0;
|
|
for (int i = 0; i < count; i++) {
|
|
const char *b = strstr(rest, "-----BEGIN CERTIFICATE-----");
|
|
if (!b) break;
|
|
const char *e = strstr(b, "-----END CERTIFICATE-----");
|
|
if (!e) break;
|
|
e += strlen("-----END CERTIFICATE-----");
|
|
|
|
size_t blklen = (size_t)(e - b);
|
|
char *blk = (char *)malloc(blklen + 1);
|
|
if (!blk) { free(roots); return PAZE_ERR_NOMEM; }
|
|
memcpy(blk, b, blklen);
|
|
blk[blklen] = '\0';
|
|
|
|
paze_status_t st = paze_x509_load_pem(&roots[loaded], blk);
|
|
free(blk);
|
|
if (st == PAZE_OK) loaded++;
|
|
rest = e;
|
|
}
|
|
|
|
if (loaded == 0) { free(roots); return PAZE_ERR_FORMAT; }
|
|
|
|
if (cfg->anchors) {
|
|
for (int i = 0; i < cfg->anchor_count; i++) paze_x509_free(&cfg->anchors[i]);
|
|
free(cfg->anchors);
|
|
}
|
|
cfg->anchors = roots; cfg->anchor_count = loaded;
|
|
return PAZE_OK;
|
|
}
|
|
|
|
paze_status_t paze_tls_config_set_verify(paze_tls_config_t *cfg,
|
|
paze_tls_verify_mode_t mode,
|
|
paze_tls_verify_cb cb, void *ctx) {
|
|
if (!cfg) return PAZE_ERR_INVALID;
|
|
cfg->verify_mode = mode;
|
|
cfg->verify_cb = cb;
|
|
cfg->verify_ctx = ctx;
|
|
return PAZE_OK;
|
|
}
|
|
|
|
paze_status_t paze_tls_config_set_versions(paze_tls_config_t *cfg,
|
|
int enable_1_3, int enable_1_2) {
|
|
if (!cfg) return PAZE_ERR_INVALID;
|
|
cfg->enable_1_3 = enable_1_3 ? 1 : 0;
|
|
cfg->enable_1_2 = enable_1_2 ? 1 : 0;
|
|
return PAZE_OK;
|
|
}
|
|
|
|
/* ============================================================
|
|
* TLS 1.3 PSK 会话恢复公开 API
|
|
* ============================================================ */
|
|
paze_status_t paze_tls_config_set_session(paze_tls_config_t *cfg,
|
|
const paze_tls_session_t *sess) {
|
|
if (!cfg || !sess) return PAZE_ERR_INVALID;
|
|
if (sess->valid && (sess->ticket_len == 0 || sess->ticket_len > sizeof(sess->ticket) ||
|
|
sess->psk_len == 0 || sess->psk_len > sizeof(sess->psk)))
|
|
return PAZE_ERR_INVALID;
|
|
cfg->session = *sess; /* 值拷贝 */
|
|
return PAZE_OK;
|
|
}
|
|
|
|
/* 握手后获取服务端下发的会话(客户端在收到 NewSessionTicket 后有效)。
|
|
* 未收到 ticket 或非 1.3 时返回 valid=0。 */
|
|
paze_status_t paze_tls_get_session(const paze_tls_t *tls, paze_tls_session_t *sess) {
|
|
if (!tls || !sess) return PAZE_ERR_INVALID;
|
|
memset(sess, 0, sizeof(*sess));
|
|
if (tls->neg.version != PAZE_TLS_VERSION_1_3 || !tls->have_ticket ||
|
|
tls->recv_ticket_len == 0 || tls->psk_len == 0)
|
|
return PAZE_OK;
|
|
if (tls->recv_ticket_len > sizeof(sess->ticket) || tls->psk_len > sizeof(sess->psk))
|
|
return PAZE_OK;
|
|
memcpy(sess->ticket, tls->recv_ticket, tls->recv_ticket_len);
|
|
sess->ticket_len = tls->recv_ticket_len;
|
|
memcpy(sess->psk, tls->psk, tls->psk_len);
|
|
sess->psk_len = tls->psk_len;
|
|
sess->age_add = tls->ticket_age_add;
|
|
sess->recv_time = tls->ticket_recv_time;
|
|
sess->hash = tls->psk_hash;
|
|
sess->valid = 1;
|
|
return PAZE_OK;
|
|
}
|
|
|
|
/* 本次握手是否通过 PSK 会话恢复(客户端/服务端均适用) */
|
|
int paze_tls_session_resumed(const paze_tls_t *tls) {
|
|
return tls ? (tls->psk_mode && tls->handshake_done) : 0;
|
|
}
|
|
|
|
/* 服务端:导出 session ticket key(位于 cfg,cfg_owned 在 paze_tls_new 时拷贝) */
|
|
paze_status_t paze_tls_get_session_ticket_key(const paze_tls_t *tls,
|
|
uint8_t key[32], int *ready) {
|
|
if (!tls || !key || !ready) return PAZE_ERR_INVALID;
|
|
paze_tls_config_t *cfg = tls->cfg_owned ? tls->cfg_owned : (paze_tls_config_t *)tls->cfg;
|
|
*ready = cfg->ticket_key_ready;
|
|
if (cfg->ticket_key_ready) memcpy(key, cfg->ticket_key, 32);
|
|
return PAZE_OK;
|
|
}
|
|
|
|
paze_status_t paze_tls_config_set_session_ticket_key(paze_tls_config_t *cfg,
|
|
const uint8_t key[32]) {
|
|
if (!cfg || !key) return PAZE_ERR_INVALID;
|
|
memcpy(cfg->ticket_key, key, 32);
|
|
cfg->ticket_key_ready = 1;
|
|
return PAZE_OK;
|
|
}
|
|
|
|
/* ============================================================
|
|
* 连接对象
|
|
* ============================================================ */
|
|
paze_tls_t *paze_tls_new(const paze_tls_config_t *cfg) {
|
|
if (!cfg) return NULL;
|
|
paze_tls_t *t = (paze_tls_t *)calloc(1, sizeof(*t));
|
|
if (!t) return NULL;
|
|
/* 配置深拷贝(配置对象是用户的,但连接需独立拥有,避免悬挂) */
|
|
t->cfg_owned = paze_tls_config_new(cfg->role);
|
|
if (!t->cfg_owned) { free(t); return NULL; }
|
|
*t->cfg_owned = *cfg; /* struct 拷贝(指针浅拷贝,所以需要重分配 alpn/cert/anchors) */
|
|
t->cfg_owned->alpn_count = 0;
|
|
for (int i = 0; i < cfg->alpn_count; i++) {
|
|
t->cfg_owned->alpn[i] = strdup(cfg->alpn[i] ? cfg->alpn[i] : "");
|
|
if (t->cfg_owned->alpn[i]) t->cfg_owned->alpn_count++;
|
|
}
|
|
if (cfg->cert_der && cfg->cert_der_len) {
|
|
t->cfg_owned->cert_der = (uint8_t *)malloc(cfg->cert_der_len);
|
|
if (t->cfg_owned->cert_der) {
|
|
memcpy(t->cfg_owned->cert_der, cfg->cert_der, cfg->cert_der_len);
|
|
t->cfg_owned->cert_der_len = cfg->cert_der_len;
|
|
}
|
|
}
|
|
/* private key:struct 赋值即可(bignum 不持 malloc) */
|
|
t->cfg_owned->privkey = cfg->privkey;
|
|
/* anchors: 不深拷贝(共享指针,要求用户 cfg 寿命 ≥ tls) */
|
|
t->cfg_owned->anchors = cfg->anchors;
|
|
t->cfg_owned->anchor_count = cfg->anchor_count;
|
|
|
|
t->cfg = t->cfg_owned;
|
|
t->role = cfg->role;
|
|
t->hs_state = PAZE_HS_INIT;
|
|
return t;
|
|
}
|
|
|
|
void paze_tls_free(paze_tls_t *tls) {
|
|
if (!tls) return;
|
|
for (int i = 0; i < tls->peer_chain_len; i++) paze_x509_free(&tls->peer_chain[i]);
|
|
/* traffic keys:aes 字段不持外部资源 */
|
|
paze_memzero(&tls->handshake_secret, sizeof(tls->handshake_secret));
|
|
paze_memzero(&tls->master_secret, sizeof(tls->master_secret));
|
|
paze_tls_config_free(tls->cfg_owned);
|
|
free(tls->ch1);
|
|
free(tls->sh1);
|
|
paze_memzero(tls, sizeof(*tls));
|
|
free(tls);
|
|
}
|
|
|
|
paze_status_t paze_tls_set_bio(paze_tls_t *tls, paze_tls_bio_t bio) {
|
|
if (!tls) return PAZE_ERR_INVALID;
|
|
tls->bio = bio;
|
|
return PAZE_OK;
|
|
}
|
|
|
|
/* ============================================================
|
|
* 握手入口(版本协商:先 1.3 → 失败返回 NOTSUP → 尝试 1.2)
|
|
* ============================================================ */
|
|
paze_status_t paze_tls_handshake(paze_tls_t *tls) {
|
|
if (!tls || !tls->bio.read || !tls->bio.write) return PAZE_ERR_INVALID;
|
|
paze_tls_config_t *cfg = tls->cfg_owned;
|
|
|
|
/* 若已完成 */
|
|
if (tls->handshake_done) return PAZE_OK;
|
|
|
|
if (cfg->enable_1_3) {
|
|
paze_status_t st;
|
|
if (cfg->role == PAZE_TLS_CLIENT) {
|
|
st = paze_tls13_client_handshake(tls);
|
|
} else {
|
|
st = paze_tls13_server_handshake(tls);
|
|
}
|
|
if (st == PAZE_OK) return PAZE_OK;
|
|
if (st != PAZE_ERR_NOTSUP) return st;
|
|
/* 1.3 不可用 → 回退 1.2 */
|
|
if (!cfg->enable_1_2) return st;
|
|
}
|
|
if (cfg->enable_1_2) {
|
|
/* 1.2 实现(tls_handshake12.c) */
|
|
if (cfg->role == PAZE_TLS_CLIENT)
|
|
return paze_tls12_client_handshake(tls);
|
|
else
|
|
return paze_tls12_server_handshake(tls);
|
|
}
|
|
return PAZE_ERR_NOTSUP;
|
|
}
|
|
|
|
/* ============================================================
|
|
* 应用数据读写
|
|
* ============================================================ */
|
|
int paze_tls_read(paze_tls_t *tls, uint8_t *buf, size_t len) {
|
|
if (!tls || !buf || len == 0) return PAZE_ERR_INVALID;
|
|
/* 先消费 app_buf 中已有的未读明文 */
|
|
if (tls->app_off < tls->app_len) {
|
|
size_t n = len < tls->app_len - tls->app_off ? len : tls->app_len - tls->app_off;
|
|
memcpy(buf, tls->app_buf + tls->app_off, n);
|
|
tls->app_off += n;
|
|
return (int)n;
|
|
}
|
|
for (;;) {
|
|
const uint8_t *data; size_t n; uint8_t ct;
|
|
paze_status_t st = paze_tls_record_read(tls, &data, &n, &ct);
|
|
if (st != PAZE_OK) {
|
|
if (st == PAZE_ERR_VERIFY) {
|
|
/* bad record MAC: 发 alert */
|
|
uint8_t al[2] = { PAZE_TLS_ALERT_FATAL, PAZE_TLS_ALERT_BAD_RECORD_MAC };
|
|
paze_tls_record_send(tls, PAZE_TLS_CT_ALERT, al, 2);
|
|
}
|
|
if (st == PAZE_ERR_IO && tls->close_recv) return 0;
|
|
return -1;
|
|
}
|
|
if (ct == PAZE_TLS_CT_APPLICATION_DATA) {
|
|
size_t take = len < n ? len : n;
|
|
memcpy(buf, data, take);
|
|
/* 剩余缓冲到 app_buf */
|
|
if (take < n) {
|
|
size_t rem = n - take;
|
|
if (rem > sizeof(tls->app_buf)) rem = sizeof(tls->app_buf);
|
|
memcpy(tls->app_buf, data + take, rem);
|
|
tls->app_len = rem; tls->app_off = 0;
|
|
}
|
|
return (int)take;
|
|
}
|
|
if (ct == PAZE_TLS_CT_ALERT) {
|
|
if (n >= 2) {
|
|
if (data[1] == PAZE_TLS_ALERT_CLOSE_NOTIFY) {
|
|
tls->close_recv = 1;
|
|
/* 回复 close_notify */
|
|
if (!tls->close_sent) {
|
|
uint8_t al[2] = { PAZE_TLS_ALERT_WARNING, PAZE_TLS_ALERT_CLOSE_NOTIFY };
|
|
paze_tls_record_send(tls, PAZE_TLS_CT_ALERT, al, 2);
|
|
tls->close_sent = 1;
|
|
}
|
|
return 0;
|
|
}
|
|
paze_tls_set_err(tls, "recv fatal alert %d", data[1]);
|
|
return -1;
|
|
}
|
|
continue;
|
|
}
|
|
if (ct == 0xFF) {
|
|
/* 已在 record_read 里被返回 app data (不应发生) */
|
|
continue;
|
|
}
|
|
/* 握手完成后不应再收到握手消息(除非 KeyUpdate/Post-Handshake Auth 等,不处理) */
|
|
}
|
|
}
|
|
|
|
int paze_tls_write(paze_tls_t *tls, const uint8_t *buf, size_t len) {
|
|
if (!tls || (!buf && len)) return PAZE_ERR_INVALID;
|
|
size_t done = 0;
|
|
while (done < len) {
|
|
size_t chunk = len - done;
|
|
if (chunk > PAZE_TLS_MAX_RECORD) chunk = PAZE_TLS_MAX_RECORD;
|
|
paze_status_t st = paze_tls_record_send(tls, PAZE_TLS_CT_APPLICATION_DATA,
|
|
buf + done, chunk);
|
|
if (st != PAZE_OK) return -1;
|
|
done += chunk;
|
|
}
|
|
return (int)done;
|
|
}
|
|
|
|
int paze_tls_is_handshake_done(const paze_tls_t *tls) {
|
|
return tls ? tls->handshake_done : 0;
|
|
}
|
|
|
|
paze_status_t paze_tls_close(paze_tls_t *tls) {
|
|
if (!tls) return PAZE_ERR_INVALID;
|
|
if (!tls->close_sent && tls->handshake_done) {
|
|
uint8_t al[2] = { PAZE_TLS_ALERT_WARNING, PAZE_TLS_ALERT_CLOSE_NOTIFY };
|
|
paze_tls_record_send(tls, PAZE_TLS_CT_ALERT, al, 2);
|
|
tls->close_sent = 1;
|
|
}
|
|
return PAZE_OK;
|
|
}
|
|
|
|
/* ============================================================
|
|
* 查询
|
|
* ============================================================ */
|
|
int paze_tls_get_version(const paze_tls_t *tls) {
|
|
return tls ? tls->neg.version : 0;
|
|
}
|
|
uint16_t paze_tls_get_cipher_suite(const paze_tls_t *tls) {
|
|
return tls ? tls->neg.cipher : 0;
|
|
}
|
|
const char *paze_tls_get_alpn(const paze_tls_t *tls) {
|
|
if (!tls || !tls->neg.alpn[0]) return NULL;
|
|
return tls->neg.alpn;
|
|
}
|
|
const char *paze_tls_get_sni(const paze_tls_t *tls) {
|
|
if (!tls) return NULL;
|
|
paze_tls_config_t *cfg = tls->cfg_owned ? tls->cfg_owned : (paze_tls_config_t *)tls->cfg;
|
|
return cfg->sni[0] ? cfg->sni : NULL;
|
|
}
|
|
const char *paze_tls_last_error(const paze_tls_t *tls) {
|
|
if (!tls || !tls->errbuf[0]) {
|
|
const char *g = paze_last_err();
|
|
return g ? g : "ok";
|
|
}
|
|
return tls->errbuf;
|
|
}
|
|
|
|
/* ============================================================
|
|
* 错误记录(外部声明见 tls_internal.h)
|
|
* ============================================================ */
|
|
void paze_tls_set_err(paze_tls_t *tls, const char *fmt, ...) {
|
|
if (!tls) return;
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vsnprintf(tls->errbuf, sizeof(tls->errbuf), fmt, ap);
|
|
va_end(ap);
|
|
PAZE_LOGE("TLS: %s", tls->errbuf);
|
|
}
|