Files
shadowsocks-libev/tests/reference/aead2022.cpp
T
Max Lv 002b4093fd C++20 port 14/14: remove the C shims, report, fixes (#3077)
* Remove ss_event and the C resolver shim

Each program's loop, signals, plugin watcher and stats timer now go
through ss::event::Program (src/event/program.hpp), held on main()'s
stack; ss-manager uses it too. ss-server resolves the manager address
with the relay resolver. With no C caller left, ss_event.cpp/.h,
resolv.cpp/.h and their C tests go; test_resolver_cpp.cpp replaces the
resolver tests against the same delayed local DNS server, and ss_ev is
now an interface target over libuv.

* Reinitialize the event loop after daemonize()

The loop is created before the programs fork into the background, and
kqueue/epoll descriptors do not survive fork(), so ss-server -f (which
ss-manager always uses) died as soon as it ran. Call uv_loop_fork() in
the child.

* Move the C codecs to tests/reference and drop uthash from ss-manager

The programs now use only the C++ codecs. The C stream/AEAD/AEAD-2022
implementations stay as the reference the differential tests and the C
unit tests compare against. Key derivation and the cipher tables used by
the doc snippets move to src/crypto/keys.{hpp,cpp}.

ss-manager keeps its servers in an ss_list (at most MAX_PORT_NUM,
looked up only on control messages) instead of a uthash table, and
tests/test_manager.py covers add/ping/list/remove end to end.

* Replace uthash and ss_list with the C++ containers

The C cache (now used only by the reference codecs) moves to
tests/reference and src/uthash.h is deleted. ACL rules and ss-manager's
servers are ListNodes on IntrusiveLists; destroying an element unlinks
it, so the free paths no longer have to remember to. core.h keeps only
ss_ip. test_rule is ported to C++ and also covers literal rules.

* Describe the C++20/libuv build in the Debian and snap metadata

* Keep clang-tidy clean in the new C++ tests

* Add --max-dns-queries / max_dns_queries

Sizes the resolver's query pool, the last of the capacity settings the
no-heap-after-startup design calls for. Defaults are unchanged: the
connection limit (plus 64 on ss-server, which also resolves for its
statistics).

* Report the C++20 rewrite: allocation, size, throughput and memory

docs/performance.md compares master (C) with the C++ engine on macOS
arm64: zero heap allocations per connection or datagram (from 27-89),
TCP throughput within noise, and about 20 MiB more RSS at 1,000 open
connections from the inline per-connection buffers. docs/cpp-core.md
and CLAUDE.md now describe the finished layout.

* Fix the MinGW build of ss-server: stat_resolver is POSIX-only

* Fix the fully static image and the build image for the C++ sources

docker/static: the C++ unit tests link the static libstdc++, whose error
paths need an unwinder that --rtlib=compiler-rt does not provide; link
llvm-libunwind. The programs themselves still need no C++ runtime.

docker/build: build in the default bundled mode with CMake instead of
against libev/udns (Alpine's Mbed TLS 4 is not supported by system
mode).

* Fall back to the system heap when a library realloc cannot be served

heap_realloc returned NULL when a block outgrew the largest size class or
the budget, and libuv aborts when growing its watcher array fails: a poll
on fd 8190 or above (about 4,000 connections) killed the process.
Move the block to the system heap instead, as malloc and calloc already
do. test_heap_guard_cpp polls fd 16380 with the heaps installed.

* Leave pool room for UDP sessions that are still closing

An evicted session keeps its pool slot until its socket's close callback
runs, so with pool capacity equal to the table's the acquire after an
eviction failed: a full table dropped new-session packets after evicting
a live session for nothing. The AEAD-2022 shared-session pool had the
same problem with entries still referenced by relay sessions.

* Time client-side TCP connections when idle, not only while connecting

ss-local, ss-tunnel and ss-redir only timed the upstream connect (as the
C relays did), so a client that never finished its SOCKS handshake, or a
stream whose peer vanished without a FIN, held a pool slot forever. With
the pool now a hard cap that pauses accepting when full, enough of them
stopped the listener for good. Every connection now starts with the idle
deadline, connected() re-arms it, and traffic refreshes it for all kinds
(the idle timeout keeps its 24 h floor, MIN_TCP_IDLE_TIMEOUT).

* Include <algorithm> for std::min in crypto/keys.cpp (CLANG64)

* test.py: accept host:port for --dns

The tunnel test hard-coded port 53, so it could only use a public
resolver or root. With a port it runs against a local DNS stand-in.

* Use the Mbed TLS compat wrappers for MD5 key derivation

* Fix the documentation build after moving the codecs

utils.cpp's \snippet aead.cpp and stream.cpp now live in src/crypto/,
which was not on Doxygen's EXAMPLE_PATH, so the man1 and HTML builds
failed. List the documentation sources explicitly instead of guessing
their extension.
2026-10-15 10:11:56 +08:00

646 lines
19 KiB
C++

/*
* aead2022.c - Shadowsocks 2022 Edition (SIP022) AEAD ciphers
*
* Copyright (C) 2013 - 2026, Max Lv <max.c.lv@gmail.com>
*
* This file is part of the shadowsocks-libev.
*
* shadowsocks-libev is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-libev is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with shadowsocks-libev; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*
* Spec: https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2022-1-shadowsocks-2022-edition.md
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <time.h>
#include <sodium.h>
#include "aead.h"
#include "aead_internal.h"
#include "cache.h"
#include "utils.h"
#include "blake3/blake3.h"
/* SIP022 constants */
#define SS2022_HDR_TYPE_CLIENT 0
#define SS2022_HDR_TYPE_SERVER 1
/* TCP decrypt state machine (cipher_ctx_t.hdr_stage) */
#define SS2022_STAGE_FIXED_HDR 0
#define SS2022_STAGE_VAR_HDR 1 /* request: variable header; response: first payload */
#define SS2022_STAGE_STREAM 2
/* request fixed-length header: type(1) + timestamp(8) + length(2) */
#define SS2022_REQ_FIXED_LEN 11
/* response fixed-length header: type(1) + timestamp(8) + request salt + length(2) */
#define SS2022_RESP_FIXED_LEN(salt_len) (11 + (salt_len))
#define SS2022_FIXED_BUF_LEN (11 + MAX_KEY_LENGTH)
#define SS2022_CHUNK_LEN_SIZE 2
#define SS2022_CHUNK_MASK 0xFFFF
#define SS2022_TAG_LEN 16
#define SS2022_SALT_POOL_CAP 65536
/*
* Role handling. AEAD-2022 is asymmetric: the client encrypts request
* streams and decrypts response streams, the server does the opposite.
* The default follows the module this file is compiled into; unit tests
* override it with aead_set_role().
*/
#ifdef MODULE_REMOTE
static int aead_role = CRYPTO_ROLE_SERVER;
#else
static int aead_role = CRYPTO_ROLE_CLIENT;
#endif
void
aead_set_role(int role)
{
aead_role = role;
}
int
aead_get_role(void)
{
return aead_role;
}
/*
* session_subkey := blake3::derive_key(context, key || salt)
* salt is the per-stream random salt for TCP, or the session ID for UDP.
*/
void
ss2022_derive_subkey(const uint8_t *key, size_t key_len,
const uint8_t *salt, size_t salt_len,
uint8_t *subkey, size_t subkey_len)
{
uint8_t material[2 * MAX_KEY_LENGTH];
blake3_hasher hasher;
memcpy(material, key, key_len);
memcpy(material + key_len, salt, salt_len);
blake3_hasher_init_derive_key(&hasher, SS2022_SUBKEY_CTX);
blake3_hasher_update(&hasher, material, key_len + salt_len);
blake3_hasher_finalize(&hasher, subkey, subkey_len);
sodium_memzero(material, sizeof(material));
}
/*
* Server-side salt pool. SIP022 requires exact salt matching with 60s of
* retention and forbids bloom filters (false positives are not allowed),
* so ppbloom cannot be reused here.
*/
static struct cache *ss2022_salt_pool = NULL;
static int
ss2022_salt_check_and_add(const uint8_t *salt, size_t salt_len)
{
if (ss2022_salt_pool == NULL) {
if (cache_create_keyed(&ss2022_salt_pool, SS2022_SALT_POOL_CAP, MAX_KEY_LENGTH, NULL) != 0)
return CRYPTO_ERROR;
}
cache_clear(ss2022_salt_pool, SS2022_SALT_RETENTION);
if (cache_key_exist(ss2022_salt_pool, (char *)salt, salt_len)) {
LOGE("crypto: AEAD-2022: repeat salt detected");
return CRYPTO_ERROR;
}
cache_insert(ss2022_salt_pool, (char *)salt, salt_len, NULL);
return CRYPTO_OK;
}
static int
ss2022_check_timestamp(uint64_t ts)
{
uint64_t now = (uint64_t)time(NULL);
uint64_t diff = now > ts ? now - ts : ts - now;
return diff <= SS2022_TIME_WINDOW ? CRYPTO_OK : CRYPTO_ERROR;
}
static void
store16_be(uint8_t *p, uint16_t v)
{
p[0] = (uint8_t)(v >> 8);
p[1] = (uint8_t)v;
}
static void
store64_be(uint8_t *p, uint64_t v)
{
for (int i = 7; i >= 0; i--) {
p[i] = (uint8_t)(v & 0xff);
v >>= 8;
}
}
static uint64_t
load64_be(const uint8_t *p)
{
uint64_t v = 0;
for (int i = 0; i < 8; i++)
v = (v << 8) | p[i];
return v;
}
/*
* Length of the SOCKS5 address (ATYP + address + port) at the head of buf,
* or 0 if it is malformed or truncated.
*/
static size_t
ss2022_socks_addr_len(const uint8_t *buf, size_t len)
{
if (len < 1)
return 0;
size_t addr_len;
switch (buf[0] & ADDRTYPE_MASK) {
case 1: /* IPv4 */
addr_len = 1 + 4;
break;
case 4: /* IPv6 */
addr_len = 1 + 16;
break;
case 3: /* domain */
if (len < 2)
return 0;
addr_len = 2 + buf[1];
break;
default:
return 0;
}
return (len >= addr_len + 2) ? addr_len + 2 : 0;
}
/* Encrypt one chunk at c, then advance the counting nonce. */
static int
ss2022_seal(cipher_ctx_t *ctx, uint8_t *c, const uint8_t *p, size_t plen)
{
size_t clen = plen + SS2022_TAG_LEN;
int err = aead_cipher_encrypt(ctx, c, &clen, (uint8_t *)p, plen,
NULL, 0, ctx->nonce, ctx->skey);
if (err)
return -1;
sodium_increment(ctx->nonce, ctx->cipher->nonce_len);
return (int)clen;
}
/* Decrypt one chunk (mlen includes the tag), then advance the nonce. */
static int
ss2022_open(cipher_ctx_t *ctx, uint8_t *p, const uint8_t *c, size_t mlen)
{
size_t plen = mlen - SS2022_TAG_LEN;
int err = aead_cipher_decrypt(ctx, p, &plen, (uint8_t *)c, mlen,
NULL, 0, ctx->nonce, ctx->skey);
if (err)
return -1;
sodium_increment(ctx->nonce, ctx->cipher->nonce_len);
return (int)plen;
}
/* ------------------------------------------------------------------ TCP */
/*
* Build the request variable-length header body:
* ATYP || address || port || padding length || padding || initial payload
* The caller's first plaintext holds the SOCKS address, optionally followed
* by initial payload bytes. SIP022 requires padding when there is no initial
* payload, so random padding is added in that case.
*/
static int
ss2022_build_req_varhdr(const uint8_t *first, size_t first_len,
uint8_t **out, size_t *out_len)
{
size_t addr_len = ss2022_socks_addr_len(first, first_len);
if (addr_len == 0) {
LOGE("AEAD-2022: malformed SOCKS address in request header");
return CRYPTO_ERROR;
}
size_t payload_len = first_len - addr_len;
uint16_t pad_len = 0;
if (payload_len == 0) {
uint16_t r;
rand_bytes(&r, sizeof(r));
pad_len = (uint16_t)(r % SS2022_MAX_PADDING) + 1;
}
size_t total = addr_len + 2 + pad_len + payload_len;
uint8_t *buf = (uint8_t *)ss_malloc(total);
memcpy(buf, first, addr_len);
store16_be(buf + addr_len, pad_len);
if (pad_len > 0)
rand_bytes(buf + addr_len + 2, pad_len);
if (payload_len > 0)
memcpy(buf + addr_len + 2 + pad_len, first + addr_len, payload_len);
*out = buf;
*out_len = total;
return CRYPTO_OK;
}
/*
* Emit salt and header chunks for the first write of a stream. The salt and
* headers are written into one buffer so the caller sends them in a single
* write, as SIP022 requires.
*/
static int
ss2022_emit_header(cipher_ctx_t *ctx, buffer_t *out, size_t *out_len,
const uint8_t *first, size_t first_len, size_t capacity)
{
size_t salt_len = ctx->cipher->key_len;
uint64_t now = (uint64_t)time(NULL);
uint8_t fixed[SS2022_FIXED_BUF_LEN];
size_t fixed_len;
uint8_t *body = NULL;
size_t body_len = 0;
int body_owned = 0;
int rc = CRYPTO_ERROR;
size_t o;
int n;
if (ctx->role == CRYPTO_ROLE_CLIENT) {
if (ss2022_build_req_varhdr(first, first_len, &body, &body_len)
!= CRYPTO_OK)
return CRYPTO_ERROR;
body_owned = 1;
if (body_len > SS2022_CHUNK_MASK) {
LOGE("AEAD-2022: request header too large");
goto done;
}
fixed_len = SS2022_REQ_FIXED_LEN;
fixed[0] = SS2022_HDR_TYPE_CLIENT;
store64_be(fixed + 1, now);
store16_be(fixed + 9, (uint16_t)body_len);
} else {
/*
* The response fixed header doubles as the first length chunk and
* echoes the request salt, which lives in the paired decrypt ctx.
*/
cipher_ctx_t *peer = ctx->peer;
if (peer == NULL || !peer->init) {
LOGE("AEAD-2022: response header needs the request salt");
return CRYPTO_ERROR;
}
if (first_len > SS2022_CHUNK_MASK) {
LOGE("AEAD-2022: response payload too large");
return CRYPTO_ERROR;
}
body = (uint8_t *)first;
body_len = first_len;
fixed_len = SS2022_RESP_FIXED_LEN(salt_len);
fixed[0] = SS2022_HDR_TYPE_SERVER;
store64_be(fixed + 1, now);
memcpy(fixed + 9, peer->salt, salt_len);
store16_be(fixed + 9 + salt_len, (uint16_t)body_len);
}
brealloc(out, salt_len + fixed_len + SS2022_TAG_LEN
+ body_len + SS2022_TAG_LEN, capacity);
memcpy(out->data, ctx->salt, salt_len);
o = salt_len;
n = ss2022_seal(ctx, (uint8_t *)out->data + o, fixed, fixed_len);
if (n < 0)
goto done;
o += n;
n = ss2022_seal(ctx, (uint8_t *)out->data + o, body, body_len);
if (n < 0)
goto done;
o += n;
*out_len = o;
rc = CRYPTO_OK;
done:
if (body_owned)
ss_free(body);
return rc;
}
int
ss2022_tcp_encrypt(buffer_t *plaintext, cipher_ctx_t *ctx, size_t capacity)
{
static buffer_t tmp = { 0, 0, 0, NULL };
if (plaintext->len == 0)
return CRYPTO_OK;
if (!ctx->init) {
aead_cipher_ctx_set_key(ctx, 1);
ctx->init = 1;
size_t out_len = 0;
int err = ss2022_emit_header(ctx, &tmp, &out_len,
(uint8_t *)plaintext->data,
plaintext->len, capacity);
if (err)
return err;
tmp.len = out_len;
bswap_data(plaintext, &tmp);
plaintext->len = out_len;
return CRYPTO_OK;
}
/* Steady state: length chunk followed by payload chunk. */
size_t remaining = plaintext->len;
uint8_t *src = (uint8_t *)plaintext->data;
size_t total_out = 0;
size_t nchunks = (plaintext->len + SS2022_CHUNK_MASK - 1) / SS2022_CHUNK_MASK;
brealloc(&tmp, plaintext->len
+ nchunks * (SS2022_CHUNK_LEN_SIZE + 2 * SS2022_TAG_LEN),
capacity);
while (remaining > 0) {
uint16_t plen = remaining > SS2022_CHUNK_MASK
? SS2022_CHUNK_MASK : (uint16_t)remaining;
uint8_t len_buf[SS2022_CHUNK_LEN_SIZE];
store16_be(len_buf, plen);
int n = ss2022_seal(ctx, (uint8_t *)tmp.data + total_out,
len_buf, SS2022_CHUNK_LEN_SIZE);
if (n < 0)
return CRYPTO_ERROR;
total_out += n;
n = ss2022_seal(ctx, (uint8_t *)tmp.data + total_out, src, plen);
if (n < 0)
return CRYPTO_ERROR;
total_out += n;
src += plen;
remaining -= plen;
}
tmp.len = total_out;
bswap_data(plaintext, &tmp);
plaintext->len = total_out;
return CRYPTO_OK;
}
/*
* Parse a decrypted request variable-length header and emit the SOCKS
* address followed by any initial payload, which is the layout the
* ss-server request path already understands.
*/
static int
ss2022_parse_req_varhdr(const uint8_t *h, size_t len, buffer_t *out,
size_t capacity)
{
size_t socks_len = ss2022_socks_addr_len(h, len);
if (socks_len == 0 || len < socks_len + 2)
return CRYPTO_ERROR;
uint16_t pad_len = load16_be(h + socks_len);
size_t pad_ofst = socks_len + 2;
if (pad_len > SS2022_MAX_PADDING || pad_ofst + pad_len > len)
return CRYPTO_ERROR;
size_t payload_ofst = pad_ofst + pad_len;
size_t payload_len = len - payload_ofst;
/* SIP022: a request with neither payload nor padding must be rejected. */
if (payload_len == 0 && pad_len == 0)
return CRYPTO_ERROR;
brealloc(out, socks_len + payload_len, capacity);
memcpy(out->data, h, socks_len);
if (payload_len > 0)
memcpy(out->data + socks_len, h + payload_ofst, payload_len);
out->len = socks_len + payload_len;
return CRYPTO_OK;
}
/* Append n bytes of freshly decrypted plaintext to the output buffer. */
static int
ss2022_drain_chunks(cipher_ctx_t *ctx, buffer_t *chunk, buffer_t *out,
size_t capacity)
{
for (;;) {
if (ctx->next_plen == 0) {
/* Need a length chunk first. */
if (chunk->len < SS2022_CHUNK_LEN_SIZE + SS2022_TAG_LEN)
return CRYPTO_OK;
uint8_t len_buf[SS2022_CHUNK_LEN_SIZE];
int n = ss2022_open(ctx, len_buf,
(uint8_t *)chunk->data + chunk->idx,
SS2022_CHUNK_LEN_SIZE + SS2022_TAG_LEN);
if (n < 0) {
LOGE("AEAD-2022: length chunk authentication failed");
return CRYPTO_ERROR;
}
uint16_t plen = load16_be(len_buf);
if (plen == 0) {
LOGE("AEAD-2022: zero-length payload chunk");
return CRYPTO_ERROR;
}
chunk->idx += SS2022_CHUNK_LEN_SIZE + SS2022_TAG_LEN;
chunk->len -= SS2022_CHUNK_LEN_SIZE + SS2022_TAG_LEN;
ctx->next_plen = plen;
}
/*
* The length chunk's nonce has already been consumed, so the pending
* length is kept in ctx->next_plen until the payload arrives.
*/
size_t need = (size_t)ctx->next_plen + SS2022_TAG_LEN;
if (chunk->len < need)
return CRYPTO_OK;
brealloc(out, out->len + ctx->next_plen, capacity);
int n = ss2022_open(ctx, (uint8_t *)out->data + out->len,
(uint8_t *)chunk->data + chunk->idx, need);
if (n < 0) {
LOGE("AEAD-2022: payload chunk authentication failed");
return CRYPTO_ERROR;
}
chunk->idx += need;
chunk->len -= need;
out->len += n;
ctx->next_plen = 0;
}
}
int
ss2022_tcp_decrypt(buffer_t *ciphertext, cipher_ctx_t *ctx, size_t capacity)
{
static buffer_t out = { 0, 0, 0, NULL };
size_t salt_len = ctx->cipher->key_len;
int is_client = (ctx->role == CRYPTO_ROLE_CLIENT);
if (ctx->chunk == NULL) {
ctx->chunk = (buffer_t *)ss_malloc(sizeof(buffer_t));
memset(ctx->chunk, 0, sizeof(buffer_t));
balloc(ctx->chunk, capacity);
}
buffer_t *chunk = ctx->chunk;
/* Accumulate the newly received ciphertext. */
if (chunk->idx > 0) {
if (chunk->len > 0)
memmove(chunk->data, chunk->data + chunk->idx, chunk->len);
chunk->idx = 0;
}
brealloc(chunk, chunk->len + ciphertext->len, capacity);
memcpy(chunk->data + chunk->len, ciphertext->data, ciphertext->len);
chunk->len += ciphertext->len;
out.len = 0;
/* Stage 0: salt and fixed-length header, consumed as one unit. */
if (!ctx->init) {
size_t fixed_len = is_client ? SS2022_RESP_FIXED_LEN(salt_len)
: SS2022_REQ_FIXED_LEN;
size_t need = salt_len + fixed_len + SS2022_TAG_LEN;
if (chunk->len < need) {
ciphertext->len = 0;
return CRYPTO_NEED_MORE;
}
memcpy(ctx->salt, chunk->data + chunk->idx, salt_len);
if (!is_client
&& ss2022_salt_check_and_add(ctx->salt, salt_len) != CRYPTO_OK)
return CRYPTO_ERROR;
aead_cipher_ctx_set_key(ctx, 0);
uint8_t fixed[SS2022_FIXED_BUF_LEN];
int n = ss2022_open(ctx, fixed,
(uint8_t *)chunk->data + chunk->idx + salt_len,
fixed_len + SS2022_TAG_LEN);
if (n < 0) {
LOGE("AEAD-2022: fixed header authentication failed");
return CRYPTO_ERROR;
}
uint8_t want_type = is_client ? SS2022_HDR_TYPE_SERVER
: SS2022_HDR_TYPE_CLIENT;
if (fixed[0] != want_type) {
LOGE("AEAD-2022: unexpected header type %u", fixed[0]);
return CRYPTO_ERROR;
}
if (ss2022_check_timestamp(load64_be(fixed + 1)) != CRYPTO_OK) {
LOGE("AEAD-2022: header timestamp out of window");
return CRYPTO_ERROR;
}
if (is_client) {
cipher_ctx_t *peer = ctx->peer;
if (peer == NULL || memcmp(fixed + 9, peer->salt, salt_len) != 0) {
LOGE("AEAD-2022: response does not echo the request salt");
return CRYPTO_ERROR;
}
ctx->next_plen = load16_be(fixed + 9 + salt_len);
} else {
ctx->hdr_varlen = load16_be(fixed + 9);
}
chunk->idx += need;
chunk->len -= need;
ctx->init = 1;
ctx->hdr_stage = SS2022_STAGE_VAR_HDR;
}
/*
* Stage 1: the request's variable-length header, or the response's first
* payload chunk whose length came from the fixed header.
*/
if (ctx->hdr_stage == SS2022_STAGE_VAR_HDR) {
uint16_t want = is_client ? ctx->next_plen : ctx->hdr_varlen;
size_t need = (size_t)want + SS2022_TAG_LEN;
if (want == 0) {
LOGE("AEAD-2022: empty header chunk");
return CRYPTO_ERROR;
}
if (chunk->len < need) {
ciphertext->len = 0;
return CRYPTO_NEED_MORE;
}
uint8_t *body = (uint8_t *)ss_malloc(want);
int n = ss2022_open(ctx, body,
(uint8_t *)chunk->data + chunk->idx, need);
if (n < 0) {
ss_free(body);
LOGE("AEAD-2022: header chunk authentication failed");
return CRYPTO_ERROR;
}
chunk->idx += need;
chunk->len -= need;
int rc = CRYPTO_OK;
if (is_client) {
brealloc(&out, n, capacity);
memcpy(out.data, body, n);
out.len = n;
} else {
rc = ss2022_parse_req_varhdr(body, n, &out, capacity);
if (rc != CRYPTO_OK)
LOGE("AEAD-2022: malformed request header");
}
ss_free(body);
if (rc != CRYPTO_OK)
return rc;
ctx->next_plen = 0;
ctx->hdr_stage = SS2022_STAGE_STREAM;
}
/* Stage 2: length chunk and payload chunk, repeated. */
if (ss2022_drain_chunks(ctx, chunk, &out, capacity) != CRYPTO_OK)
return CRYPTO_ERROR;
if (chunk->len == 0)
chunk->idx = 0;
if (out.len == 0) {
ciphertext->len = 0;
return CRYPTO_NEED_MORE;
}
brealloc(ciphertext, out.len, capacity);
memcpy(ciphertext->data, out.data, out.len);
ciphertext->len = out.len;
return CRYPTO_OK;
}