• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

libbitcoin / libbitcoin-network / 41958321791

05 Nov 2026 02:55AM UTC coverage: 93.92% (-0.05%) from 93.971%
41958321791

push

github

web-flow
Merge pull request #1007 from evoskuil/master

IO optimizations.

42 of 42 new or added lines in 2 files covered. (100.0%)

9 existing lines in 3 files now uncovered.

14536 of 15477 relevant lines covered (93.92%)

2482.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.31
/src/net/socket_ws.cpp
1
/**
2
 * Copyright (c) 2011-2026 libbitcoin developers
3
 *
4
 * This file is part of libbitcoin.
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Affero General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU Affero General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Affero General Public License
17
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19
#include <bitcoin/network/net/socket.hpp>
20

21
#include <bitcoin/network/define.hpp>
22
#include <bitcoin/network/log/log.hpp>
23
#include <bitcoin/network/settings.hpp>
24
#include <bitcoin/network/ws/ws.hpp>
25

26
namespace libbitcoin {
27
namespace network {
28

29
using namespace system;
30
using namespace std::placeholders;
31

32
BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
33
BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR)
34

35
// WS (read).
36
// ----------------------------------------------------------------------------
37
// The flat_buffer's max_size() must be large enough to hold the complete ws
38
// framed message. The read will fail if the message exceeds this limit.
39

40
void socket::ws_read(http::flat_buffer& out,
5 ✔
41
    count_handler&& handler) NOEXCEPT
42
{
43
    io::dispatch(strand_,
5 ✔
44
        std::bind(&socket::do_ws_read,
10 ✔
45
            shared_from_this(), std::ref(out), std::move(handler)));
10 ✔
46
}
5 ✔
47

48
// private
49
void socket::do_ws_read(ref<http::flat_buffer> out,
5 ✔
50
    const count_handler& handler) NOEXCEPT
51
{
52
    BC_ASSERT(stranded());
5 ✔
53
    async_read(out.get(), handler);
5 ✔
54
}
5 ✔
55

56
// WS (write).
57
// ----------------------------------------------------------------------------
58

59
void socket::ws_write(const io::const_buffer& in, bool binary,
118 ✔
60
    count_handler&& handler) NOEXCEPT
61
{
62
    io::dispatch(strand_,
118 ✔
63
        std::bind(&socket::do_ws_write,
236 ✔
64
            shared_from_this(), in, binary, std::move(handler)));
236 ✔
65
}
118 ✔
66

67
// private
68
void socket::do_ws_write(const io::const_buffer& in, bool binary,
118 ✔
69
    const count_handler& handler) NOEXCEPT
70
{
71
    BC_ASSERT(stranded());
118 ✔
72
    async_write(in, binary, true, handler);
118 ✔
73
}
118 ✔
74

75
// WS (upgrade).
76
// ----------------------------------------------------------------------------
77

78
// The response is written before the upgrade (or refusal) is returned.
79
code socket::accept_websocket(const http::request& request) NOEXCEPT
24 ✔
80
{
81
    BC_ASSERT(stranded());
24 ✔
82
    BC_ASSERT(!websocket());
24 ✔
83

84
    ws::response response{};
24 ✔
85
    const auto ec = ws::accept(response, request.base());
24 ✔
86
    response.set(http::field::server, BC_HTTP_SERVER_NAME);
24 ✔
87

88
    http::serializer<false, http::string_body> serializer{ response };
24 ✔
89
    data_chunk bytes{};
24 ✔
90
    code error{};
24 ✔
91
    while (!serializer.is_done() && !error)
48 ✔
92
        for (const auto& buffer: serializer.next(error))
51 ✔
93
            bytes.insert(bytes.end(), pointer_cast<const uint8_t>(
27 ✔
94
                buffer.data()), std::next(pointer_cast<const uint8_t>(
95
                    buffer.data()), buffer.size()));
51 ✔
96

97
    VARIANT_DISPATCH_FUNCTION(io::write, get_tcp(),
48 ✔
98
        io::buffer(bytes), error);
99

100
    if (error || ec)
24 ✔
101
        return error::operation_failed;
3 ✔
102

103
    ws_reader_.emplace(maximum_, true);
21 ✔
104
    ws_input_.clear();
21 ✔
105
    ws_continuing_ = false;
21 ✔
106
    ws_closing_ = false;
21 ✔
107
    websocket_.store(true);
21 ✔
108
    return error::success;
21 ✔
109
}
24 ✔
110

111
// WS (framing).
112
// ----------------------------------------------------------------------------
113
// A read assembles one data message, answering pings and the peer's close.
114
// Frames are written in order, so control frames queue behind data frames.
115

116
// private
117
void socket::do_ws_read_message(const ref<http::flat_buffer>& out,
18 ✔
118
    const count_handler& handler) NOEXCEPT
119
{
120
    BC_ASSERT(stranded());
18 ✔
121

122
    while (true)
123
    {
124
        ws::reader::event event{};
20 ✔
125
        code ec{};
20 ✔
126
        const auto data = ws_input_.data();
20 ✔
127
        const std::span<const uint8_t> bytes{ pointer_cast<const uint8_t>(
128
            data.data()), data.size() };
20 ✔
129

130
        const auto used = ws_reader_->put(bytes, out.get(), event, ec);
20 ✔
131
        ws_input_.consume(used);
20 ✔
132

133
        if (ec == error::need_more)
20 ✔
134
            break;
135

136
        if (ec)
10 ✔
137
        {
138
            handler(ec, out.get().size());
×
139
            return;
8 ✔
140
        }
141

142
        const auto& payload = ws_reader_->control();
10 ✔
143
        switch (event)
10 ✔
144
        {
145
            case ws::reader::event::message:
7 ✔
146
                handler(error::success, out.get().size());
7 ✔
147
                return;
7 ✔
148
            case ws::reader::event::ping:
1 ✔
149
                do_ws_event(ws::opcode::ping, payload);
1 ✔
150
                ws_pong(payload);
1 ✔
151
                continue;
3 ✔
152
            case ws::reader::event::pong:
1 ✔
153
                do_ws_event(ws::opcode::pong, payload);
1 ✔
154
                continue;
1 ✔
155
            case ws::reader::event::close:
1 ✔
156
            {
157
                do_ws_event(ws::opcode::close, payload);
1 ✔
158
                if (ws_closing_)
1 ✔
159
                {
160
                    handler(error::websocket_closed, out.get().size());
×
161
                    return;
×
162
                }
163

164
                // The close is answered with the peer's close code.
165
                ws_closing_ = true;
1 ✔
166
                const auto echo = payload.size() < two ? data_chunk{} :
1 ✔
167
                    data_chunk{ payload.begin(), std::next(payload.begin(),
1 ✔
168
                        two) };
1 ✔
169

170
                ws_send(ws::opcode::close, echo,
1 ✔
171
                    std::bind(&socket::handle_ws_echo,
2 ✔
172
                        shared_from_this(), _1, _2, out, handler));
2 ✔
173
                return;
174
            }
1 ✔
175
            case ws::reader::event::none:
×
176
            default:
177
                continue;
×
178
        }
2 ✔
179
    }
2 ✔
180

181
    const auto limit = floored_subtract(ws_input_.max_size(),
10 ✔
182
        ws_input_.size());
10 ✔
183
    const auto size = std::min(limit, std::max(ws_input_.capacity() -
20 ✔
184
        ws_input_.size(), header_));
10 ✔
185

186
    try
187
    {
188
        VARIANT_DISPATCH_METHOD(get_tcp(),
20 ✔
189
            async_read_some(ws_input_.prepare(size),
190
                std::bind(&socket::handle_ws_read_message,
191
                    shared_from_this(), _1, _2, out, handler)));
192
    }
193
    catch (const std::exception& e)
×
194
    {
195
        LOGF("Exception @ ws_read: " << e.what());
×
196
        handler(error::operation_failed, {});
×
197
    }
×
198
}
199

200
// private
201
void socket::handle_ws_read_message(const code& ec, size_t size,
10 ✔
202
    const ref<http::flat_buffer>& out, const count_handler& handler) NOEXCEPT
203
{
204
    BC_ASSERT(stranded());
10 ✔
205

206
    if (ec == error::operation_canceled)
10 ✔
207
    {
208
        handler(error::channel_stopped, out.get().size());
×
209
        return;
×
210
    }
211

212
    if (ec)
10 ✔
213
    {
UNCOV
214
        if (ec == error::unknown) logx("ws-read", ec);
×
UNCOV
215
        handler(ec, out.get().size());
×
UNCOV
216
        return;
×
217
    }
218

219
    ws_input_.commit(size);
10 ✔
220
    do_ws_read_message(out, handler);
10 ✔
221
}
222

223
// private
224
// The server closes the connection once the closing handshake is complete.
225
void socket::handle_ws_echo(const code&, size_t,
1 ✔
226
    const ref<http::flat_buffer>& out, const count_handler& handler) NOEXCEPT
227
{
228
    BC_ASSERT(stranded());
1 ✔
229

230
    code ignore{};
1 ✔
231
    get_base().shutdown(io::socket::shutdown_type::send, ignore);
1 ✔
232
    handler(error::websocket_closed, out.get().size());
1 ✔
233
}
1 ✔
234

235
// private
236
// A data frame continues the message of a preceding unfinished frame.
237
void socket::ws_send_data(const io::const_buffer& data, bool binary,
130 ✔
238
    bool finish, const count_handler& handler) NOEXCEPT
239
{
240
    BC_ASSERT(stranded());
130 ✔
241

242
    const auto code = ws_continuing_ ? ws::opcode::continuation :
130 ✔
243
        (binary ? ws::opcode::binary : ws::opcode::text);
5 ✔
244

245
    ws_continuing_ = !finish;
130 ✔
246
    const auto frame = emplace_shared<ws_frame>();
130 ✔
247
    frame->header = ws::serialize_header(code, finish,
260 ✔
248
        possible_wide_cast<uint64_t>(data.size()));
130 ✔
249
    frame->data = data;
130 ✔
250
    frame->handler = handler;
130 ✔
251
    ws_queue_.push_back(frame);
130 ✔
252
    ws_write_next();
130 ✔
253
}
130 ✔
254

255
// private
256
void socket::ws_send(ws::opcode code, const data_chunk& payload,
3 ✔
257
    const count_handler& handler) NOEXCEPT
258
{
259
    BC_ASSERT(stranded());
3 ✔
260

261
    const auto frame = emplace_shared<ws_frame>();
3 ✔
262
    frame->header = ws::serialize_header(code, true,
6 ✔
263
        possible_wide_cast<uint64_t>(payload.size()));
3 ✔
264
    frame->payload = payload;
3 ✔
265
    frame->handler = handler;
3 ✔
266
    ws_queue_.push_back(frame);
3 ✔
267
    ws_write_next();
3 ✔
268
}
3 ✔
269

270
// private
271
// An unsent pong answers only the most recent ping (rfc6455 5.5.3).
272
void socket::ws_pong(const data_chunk& payload) NOEXCEPT
1 ✔
273
{
274
    BC_ASSERT(stranded());
1 ✔
275

276
    const auto queued = !is_null(ws_pong_);
1 ✔
277
    if (!queued)
1 ✔
278
        ws_pong_ = emplace_shared<ws_frame>();
1 ✔
279

280
    ws_pong_->header = ws::serialize_header(ws::opcode::pong, true,
2 ✔
281
        possible_wide_cast<uint64_t>(payload.size()));
1 ✔
282
    ws_pong_->payload = payload;
1 ✔
283

284
    if (!queued)
1 ✔
285
    {
286
        ws_queue_.push_back(ws_pong_);
1 ✔
287
        ws_write_next();
1 ✔
288
    }
289
}
1 ✔
290

291
// private
292
void socket::ws_write_next() NOEXCEPT
268 ✔
293
{
294
    BC_ASSERT(stranded());
268 ✔
295

296
    if (ws_writing_ || ws_queue_.empty())
268 ✔
297
        return;
134 ✔
298

299
    ws_writing_ = true;
134 ✔
300
    const auto frame = ws_queue_.front();
134 ✔
301
    if (frame == ws_pong_)
134 ✔
302
        ws_pong_.reset();
1 ✔
303
    const auto payload = frame->payload.empty() ? frame->data :
134 ✔
304
        io::const_buffer{ frame->payload.data(), frame->payload.size() };
134 ✔
305

306
    frame->buffers =
268 ✔
307
    {
308
        io::const_buffer{ frame->header.data(), frame->header.size() },
134 ✔
309
        payload
310
    };
311

312
    VARIANT_DISPATCH_FUNCTION(io::async_write, get_tcp(),
268 ✔
313
        frame->buffers, std::bind(&socket::handle_ws_frame,
314
            shared_from_this(), _1, _2, frame));
315
}
134 ✔
316

317
// private
318
void socket::handle_ws_frame(const code& ec, size_t,
134 ✔
319
    const ws_frame::ptr& frame) NOEXCEPT
320
{
321
    BC_ASSERT(stranded());
134 ✔
322

323
    ws_writing_ = false;
134 ✔
324
    ws_queue_.pop_front();
134 ✔
325
    const auto code = (ec == error::operation_canceled) ? error::channel_stopped :
134 ✔
326
        ec;
134 ✔
327

328
    if (code == error::unknown)
134 ✔
329
        logx("ws-write", ec);
×
330

331
    // A failed write fails the frames that follow it. These are removed before
332
    // any handler runs, as a handler may queue (and write) another frame.
333
    std::deque<ws_frame::ptr> failed{};
134 ✔
334
    if (code)
134 ✔
335
    {
336
        ws_pong_.reset();
×
337
        std::swap(failed, ws_queue_);
×
338
    }
339

340
    // The payload size is reported (as with stream writes).
341
    const auto size = frame->payload.empty() ? frame->data.size() :
134 ✔
342
        frame->payload.size();
4 ✔
343

344
    if (frame->handler)
134 ✔
345
        frame->handler(code, size);
133 ✔
346

347
    for (const auto& next: failed)
134 ✔
348
        if (next->handler)
×
349
            next->handler(code, zero);
×
350

351
    if (!code)
134 ✔
352
        ws_write_next();
134 ✔
353
}
134 ✔
354

355
// WS (events).
356
// ----------------------------------------------------------------------------
357

358
// private
359
void socket::do_ws_event(ws::opcode kind, const data_chunk& data) NOEXCEPT
3 ✔
360
{
361
    BC_ASSERT(stranded());
3 ✔
362

363
    switch (kind)
3 ✔
364
    {
365
        case ws::opcode::ping:
1 ✔
366
            LOGX("WS ping [" << endpoint() << "] size: " << data.size());
1 ✔
367
            break;
1 ✔
368
        case ws::opcode::pong:
1 ✔
369
            LOGX("WS pong [" << endpoint() << "] size: " << data.size());
1 ✔
370
            break;
1 ✔
371
        case ws::opcode::close:
1 ✔
372
        {
373
            uint16_t value{};
1 ✔
374
            std::string reason{};
1 ✔
375
            code ignore{};
1 ✔
376
            ws::parse_close(value, reason, data, ignore);
1 ✔
377
            LOGX("WS close [" << endpoint() << "] " << value << " "
1 ✔
378
                << reason);
379
            break;
380
        }
1 ✔
381
        default:
382
            break;
383
    }
384
}
3 ✔
385

386
BC_POP_WARNING()
387
BC_POP_WARNING()
388

389
} // namespace network
390
} // namespace libbitcoin
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc