Fix TLS read buffer in ws client: buffer excess SSL_read bytes to prevent loss of multiple WebSocket frames in a single TLS record
This commit is contained in:
+13
-2
@@ -2,10 +2,10 @@
|
||||
#define NOSTR_CORE_H
|
||||
|
||||
// Version information (auto-updated by increment_and_push.sh)
|
||||
#define VERSION "v0.6.10"
|
||||
#define VERSION "v0.6.11"
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR 6
|
||||
#define VERSION_PATCH 10
|
||||
#define VERSION_PATCH 11
|
||||
|
||||
/*
|
||||
* NOSTR Core Library - Complete API Reference
|
||||
@@ -304,6 +304,17 @@ cJSON** nostr_relay_pool_query_sync(
|
||||
cJSON* filter,
|
||||
int* event_count,
|
||||
int timeout_ms);
|
||||
// Same as above but also reports whether all relays sent EOSE (vs timed out).
|
||||
// out_got_eose is set to 1 if all connected relays sent EOSE, 0 if any timed
|
||||
// out. May be NULL if the caller doesn't care.
|
||||
cJSON** nostr_relay_pool_query_sync_with_eose(
|
||||
nostr_relay_pool_t* pool,
|
||||
const char** relay_urls,
|
||||
int relay_count,
|
||||
cJSON* filter,
|
||||
int* event_count,
|
||||
int timeout_ms,
|
||||
int* out_got_eose);
|
||||
cJSON* nostr_relay_pool_get_event(
|
||||
nostr_relay_pool_t* pool,
|
||||
const char** relay_urls,
|
||||
|
||||
@@ -57,6 +57,15 @@ typedef struct {
|
||||
SSL_CTX* ssl_ctx;
|
||||
SSL* ssl;
|
||||
int ssl_connected;
|
||||
/* Internal read buffer: SSL_read may return more bytes than the
|
||||
* caller requested (a full TLS record at once). Without buffering
|
||||
* the excess, those bytes are lost — which breaks ws_receive_frame
|
||||
* when it asks for a 2-byte header but SSL_read returns a full
|
||||
* event message. This caused the caching backfill to only receive
|
||||
* 1 event per query from TLS relays. */
|
||||
char read_buf[65536];
|
||||
size_t read_buf_len; /* total bytes in read_buf */
|
||||
size_t read_buf_pos; /* offset of next byte to consume */
|
||||
} tls_transport_t;
|
||||
|
||||
// WebSocket client structure
|
||||
@@ -708,10 +717,27 @@ static int tls_recv(void* ctx, void* data, size_t len, int timeout_ms) {
|
||||
if (!tls->ssl_connected || !tls->ssl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Always use select() to ensure proper blocking behavior
|
||||
// If SSL has pending data, use zero timeout to return immediately
|
||||
// Otherwise use the full timeout to block until data arrives
|
||||
|
||||
/* Serve from the internal read buffer first. SSL_read may have
|
||||
* returned more bytes than the caller requested on a previous call
|
||||
* (a full TLS record), and we buffered the excess. Without this,
|
||||
* ws_receive_frame loses the extra bytes when it asks for a 2-byte
|
||||
* header but SSL_read returns a full event message. */
|
||||
if (tls->read_buf_pos < tls->read_buf_len) {
|
||||
size_t avail = tls->read_buf_len - tls->read_buf_pos;
|
||||
size_t to_copy = (avail < len) ? avail : len;
|
||||
memcpy(data, tls->read_buf + tls->read_buf_pos, to_copy);
|
||||
tls->read_buf_pos += to_copy;
|
||||
if (tls->read_buf_pos >= tls->read_buf_len) {
|
||||
tls->read_buf_len = 0;
|
||||
tls->read_buf_pos = 0;
|
||||
}
|
||||
return (int)to_copy;
|
||||
}
|
||||
|
||||
/* Buffer is empty — refill it with a single SSL_read, then serve
|
||||
* the requested bytes from it. This ensures excess bytes are
|
||||
* preserved for subsequent tls_recv calls. */
|
||||
if (timeout_ms > 0) {
|
||||
fd_set readfds;
|
||||
struct timeval tv;
|
||||
@@ -719,7 +745,6 @@ static int tls_recv(void* ctx, void* data, size_t len, int timeout_ms) {
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(tls->socket_fd, &readfds);
|
||||
|
||||
// If SSL has buffered data, use zero timeout; otherwise use full timeout
|
||||
if (SSL_pending(tls->ssl) > 0) {
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 0;
|
||||
@@ -741,16 +766,26 @@ static int tls_recv(void* ctx, void* data, size_t len, int timeout_ms) {
|
||||
const int max_attempts = 10;
|
||||
|
||||
while (attempts < max_attempts) {
|
||||
int ret = SSL_read(tls->ssl, data, len);
|
||||
/* Read into the internal buffer (up to its capacity), then copy
|
||||
* the requested number of bytes to the caller's output. */
|
||||
int ret = SSL_read(tls->ssl, tls->read_buf, sizeof(tls->read_buf));
|
||||
|
||||
if (ret > 0) {
|
||||
return ret; // Success
|
||||
tls->read_buf_len = (size_t)ret;
|
||||
tls->read_buf_pos = 0;
|
||||
size_t to_copy = (tls->read_buf_len < len) ? tls->read_buf_len : len;
|
||||
memcpy(data, tls->read_buf, to_copy);
|
||||
tls->read_buf_pos = to_copy;
|
||||
if (tls->read_buf_pos >= tls->read_buf_len) {
|
||||
tls->read_buf_len = 0;
|
||||
tls->read_buf_pos = 0;
|
||||
}
|
||||
return (int)to_copy;
|
||||
}
|
||||
|
||||
int err = SSL_get_error(tls->ssl, ret);
|
||||
|
||||
if (err == SSL_ERROR_WANT_READ) {
|
||||
// Check if more data is available on socket
|
||||
fd_set readfds;
|
||||
struct timeval tv = {0, 100000}; // 100ms timeout
|
||||
|
||||
@@ -763,7 +798,7 @@ static int tls_recv(void* ctx, void* data, size_t len, int timeout_ms) {
|
||||
}
|
||||
|
||||
attempts++;
|
||||
continue; // Retry the SSL read
|
||||
continue;
|
||||
|
||||
} else if (err == SSL_ERROR_WANT_WRITE) {
|
||||
return -1;
|
||||
|
||||
Reference in New Issue
Block a user