Files
David Benjamin 908bdb84c5 Test async BIO_flush and fix a corner case
We were simulating non-blocking writes, but not non-blocking flush.
Model it as consuming one byte in AsyncBio.

In doing so, fix an obscure corner case in DTLS: If flushing after ACK
blocked, the next try would write a new ACK to the transport. There's no
real harm in this (we're running over UDP), but our tests intentionally
check for exactly the right number of writes and this was easy to fix.

This completely doesn't matter as a non-blocking writes on UDP-like
sockets are not really a thing, much less buffered non-blocking writes
on them. I don't even concretely know of anyone relying on BIO_flush in
TCP-like BIOs anymore in libssl. But since we try to support it, we
should test that we get it right.

Fixed: 381906252
Change-Id: I5296fcb01ca409d3026ca1150d6bdeaccc868014
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/81348
Auto-Submit: David Benjamin <davidben@google.com>
Reviewed-by: Lily Chen <chlily@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
2025-08-19 11:13:18 -07:00

195 lines
4.4 KiB
C++

// Copyright 2014 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "async_bio.h"
#include <errno.h>
#include <string.h>
#include <openssl/bio.h>
#include <openssl/mem.h>
#include "../../crypto/internal.h"
namespace {
struct AsyncBio {
bool datagram = false;
size_t read_quota = 0;
size_t write_quota = 0;
};
static int AsyncBioMethodType() {
static int type = [] {
int idx = BIO_get_new_index();
BSSL_CHECK(idx > 0);
return idx | BIO_TYPE_FILTER;
}();
return type;
}
AsyncBio *GetData(BIO *bio) {
if (BIO_method_type(bio) != AsyncBioMethodType()) {
return nullptr;
}
return static_cast<AsyncBio *>(BIO_get_data(bio));
}
static int AsyncWrite(BIO *bio, const char *in, int inl) {
AsyncBio *a = GetData(bio);
BIO *next = BIO_next(bio);
if (a == nullptr || next == nullptr) {
return 0;
}
BIO_clear_retry_flags(bio);
if (a->write_quota == 0) {
BIO_set_retry_write(bio);
errno = EAGAIN;
return -1;
}
if (!a->datagram && static_cast<size_t>(inl) > a->write_quota) {
inl = static_cast<int>(a->write_quota);
}
int ret = BIO_write(next, in, inl);
if (ret <= 0) {
BIO_copy_next_retry(bio);
} else {
a->write_quota -= (a->datagram ? 1 : ret);
}
return ret;
}
static int AsyncRead(BIO *bio, char *out, int outl) {
AsyncBio *a = GetData(bio);
BIO *next = BIO_next(bio);
if (a == nullptr || next == nullptr) {
return 0;
}
BIO_clear_retry_flags(bio);
if (a->read_quota == 0) {
BIO_set_retry_read(bio);
errno = EAGAIN;
return -1;
}
if (!a->datagram && static_cast<size_t>(outl) > a->read_quota) {
outl = static_cast<int>(a->read_quota);
}
int ret = BIO_read(next, out, outl);
if (ret <= 0) {
BIO_copy_next_retry(bio);
} else {
a->read_quota -= (a->datagram ? 1 : ret);
}
return ret;
}
static long AsyncCtrl(BIO *bio, int cmd, long num, void *ptr) {
AsyncBio *a = GetData(bio);
BIO *next = BIO_next(bio);
if (next == nullptr) {
return 0;
}
BIO_clear_retry_flags(bio);
// Model an async flush as consuming one byte of write quota.
if (cmd == BIO_CTRL_FLUSH && a->write_quota == 0) {
BIO_set_retry_write(bio);
errno = EAGAIN;
return -1;
}
long ret = BIO_ctrl(next, cmd, num, ptr);
if (cmd == BIO_CTRL_FLUSH && ret > 0) {
a->write_quota--;
}
BIO_copy_next_retry(bio);
return ret;
}
static int AsyncNew(BIO *bio) {
BIO_set_data(bio, new AsyncBio);
BIO_set_init(bio, 1);
return 1;
}
static int AsyncFree(BIO *bio) {
if (bio == nullptr) {
return 0;
}
delete GetData(bio);
BIO_set_data(bio, nullptr);
BIO_set_init(bio, 0);
return 1;
}
static long AsyncCallbackCtrl(BIO *bio, int cmd, BIO_info_cb *fp) {
BIO *next = BIO_next(bio);
if (next == nullptr) {
return 0;
}
return BIO_callback_ctrl(next, cmd, fp);
}
static const BIO_METHOD *AsyncBioMethod() {
static const BIO_METHOD *method = [] {
BIO_METHOD *ret = BIO_meth_new(AsyncBioMethodType(), "async bio");
BSSL_CHECK(ret);
BSSL_CHECK(BIO_meth_set_write(ret, AsyncWrite));
BSSL_CHECK(BIO_meth_set_read(ret, AsyncRead));
BSSL_CHECK(BIO_meth_set_ctrl(ret, AsyncCtrl));
BSSL_CHECK(BIO_meth_set_create(ret, AsyncNew));
BSSL_CHECK(BIO_meth_set_destroy(ret, AsyncFree));
BSSL_CHECK(BIO_meth_set_callback_ctrl(ret, AsyncCallbackCtrl));
return ret;
}();
return method;
}
} // namespace
bssl::UniquePtr<BIO> AsyncBioCreate() {
return bssl::UniquePtr<BIO>(BIO_new(AsyncBioMethod()));
}
bssl::UniquePtr<BIO> AsyncBioCreateDatagram() {
bssl::UniquePtr<BIO> ret(BIO_new(AsyncBioMethod()));
if (!ret) {
return nullptr;
}
GetData(ret.get())->datagram = true;
return ret;
}
void AsyncBioAllowRead(BIO *bio, size_t count) {
AsyncBio *a = GetData(bio);
if (a == nullptr) {
return;
}
a->read_quota += count;
}
void AsyncBioAllowWrite(BIO *bio, size_t count) {
AsyncBio *a = GetData(bio);
if (a == nullptr) {
return;
}
a->write_quota += count;
}