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

libbitcoin / libbitcoin-server / 42675974756

09 Nov 2026 06:39PM UTC coverage: 95.065% (+0.1%) from 94.917%
42675974756

push

github

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

Check cpu extensions at startup, normalize http response path.

178 of 192 new or added lines in 10 files covered. (92.71%)

11983 of 12605 relevant lines covered (95.07%)

246.05 hits per line

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

93.58
/src/protocols/admin/protocol_admin.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/server/protocols/protocol_admin.hpp>
20

21
#include <atomic>
22
#include <chrono>
23
#include <bitcoin/server/define.hpp>
24
#include <bitcoin/server/parsers/parsers.hpp>
25
#include <bitcoin/server/serializers/serializers.hpp>
26
#include <bitcoin/server/utilities/utilities.hpp>
27

28
namespace libbitcoin {
29
namespace server {
30

31
using namespace system;
32
using namespace network;
33
using namespace network::http;
34
using namespace std::placeholders;
35
constexpr auto relaxed = std::memory_order_relaxed;
36

37
#define CLASS protocol_admin
38
#define SUBSCRIBE_ADMIN(method, ...) \
39
    subscribe<CLASS>(&CLASS::method, __VA_ARGS__)
40

41
// Limited by json number domain and sentinel bit.
42
static_assert(node::events::unknown < 53u);
43
static_assert(network::levels::verbose < 53u);
44

45
// Start.
46
// ----------------------------------------------------------------------------
47

48
void protocol_admin::start() NOEXCEPT
27 ✔
49
{
50
    BC_ASSERT(stranded());
27 ✔
51

52
    if (started())
27 ✔
53
        return;
54

55
    // Subscription methods.
56
    SUBSCRIBE_ADMIN(handle_get_log_subscribe, _1, _2, _3, _4);
27 ✔
57
    SUBSCRIBE_ADMIN(handle_get_event_subscribe, _1, _2, _3, _4);
27 ✔
58
    protocol_html::start();
27 ✔
59
}
60

61
void protocol_admin::stopping(const code& ec) NOEXCEPT
27 ✔
62
{
63
    BC_ASSERT(stranded());
27 ✔
64
    log_interest_->set(zero);
27 ✔
65
    dispatcher_.stop(ec);
27 ✔
66
    protocol_html::stopping(ec);
27 ✔
67
}
27 ✔
68

69
// Dispatch.
70
// ----------------------------------------------------------------------------
71

72
bool protocol_admin::try_dispatch_object(const http::request& request) NOEXCEPT
17 ✔
73
{
74
    BC_ASSERT(stranded());
17 ✔
75

76
    BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
77
    const auto target = request.target();
17 ✔
78
    BC_POP_WARNING()
79

80
    rpc::request_t model{};
17 ✔
81
    if (const auto ec = admin_target(model, target))
17 ✔
82
    {
83
        // Allow invalid interface target to be retried as a page request.
84
        return !ec;
1 ✔
85
    }
86

87
    // No media defaults injected for an http request.
88
    if (!admin_query(model, request))
16 ✔
89
    {
90
        send_bad_request();
4 ✔
91
        return true;
92
    }
93

94
    const auto media = strip_media(model);
12 ✔
95
    if (media == media_type::unknown)
12 ✔
96
    {
97
        send_not_acceptable();
1 ✔
98
        return true;
99
    }
100

101
    // Falls through to html page dispatch.
102
    if (media == media_type::text_html)
11 ✔
103
        return false;
104

105
    // The credential may be restricted to a subset of interface methods.
106
    if (!permitted(model.method))
10 ✔
107
    {
108
        send_forbidden();
1 ✔
109
        return true;
110
    }
111

112
    if (dispatcher_.notify(model))
9 ✔
NEW
113
        send_bad_request();
×
114

115
    return true;
116
}
17 ✔
117

118
void protocol_admin::dispatch_websocket(const http::request& request) NOEXCEPT
17 ✔
119
{
120
    BC_ASSERT(stranded());
17 ✔
121

122
    // ws frames carry no headers, so ws authorization is enforced here, not
123
    // by the channel (established by basic auth on the upgrade request).
124
    if (!authorized())
17 ✔
125
    {
126
        stop(network::error::unauthorized);
×
127
        return;
4 ✔
128
    }
129

130
    // Admin websocket interface supports only string requests.
131
    if (!request.body().contains<http::string_value>())
17 ✔
132
    {
133
        stop(network::error::not_acceptable);
×
134
        return;
×
135
    }
136

137
    // Target with query string is passed via websocket string body.
138
    const auto target = request.body().get<http::string_value>();
17 ✔
139

140
    rpc::request_t model{};
17 ✔
141
    if (const auto ec = admin_target(model, target))
17 ✔
142
    {
143
        stop(network::error::bad_request);
2 ✔
144
        return;
2 ✔
145
    }
146

147
    // Default to json by simulating a json accept header (format overrides).
148
    if (!admin_query(model, target, { media_type::application_json }))
15 ✔
149
    {
150
        stop(network::error::bad_request);
1 ✔
151
        return;
1 ✔
152
    }
153

154
    if (strip_media(model) != media_type::application_json)
14 ✔
155
    {
156
        stop(network::error::not_acceptable);
1 ✔
157
        return;
1 ✔
158
    }
159

160
    // The credential may be restricted to a subset of interface methods.
161
    if (!permitted(model.method))
13 ✔
162
    {
163
        stop(network::error::unauthorized);
×
164
        return;
×
165
    }
166

167
    if (dispatcher_.notify(model))
13 ✔
168
        stop(network::error::bad_request);
×
169
}
17 ✔
170

171
// Interface handlers.
172
// ----------------------------------------------------------------------------
173

174
bool protocol_admin::handle_get_log_subscribe(const code& ec,
41 ✔
175
    interface::log_subscribe, uint8_t /*version*/, uint64_t filter) NOEXCEPT
176
{
177
    BC_ASSERT(stranded());
41 ✔
178

179
    if (stopped(ec))
41 ✔
180
        return false;
181

182
    // log outlives node, so cannot hold shared_ptr to the protocol.
183
    const auto previous = log_interest_->set(filter);
14 ✔
184
    if (!is_zero(filter) && !log_subscribed_)
14 ✔
185
    {
186
        auto handler = BIND_WEAK(handle_log, _1, _2, _3, _4);
9 ✔
187
        log.subscribe_messages(log_interest_, std::move(handler));
9 ✔
188
        log_subscribed_ = true;
9 ✔
189
    }
9 ✔
190

191
    send_json({ { "previous", previous } }, 42);
14 ✔
192
    return true;
14 ✔
193
}
194

195
bool protocol_admin::handle_get_event_subscribe(const code& ec,
35 ✔
196
    interface::event_subscribe, uint8_t /*version*/, uint64_t filter) NOEXCEPT
197
{
198
    BC_ASSERT(stranded());
35 ✔
199

200
    if (stopped(ec))
35 ✔
201
        return false;
202

203
    // log outlives node, so cannot hold shared_ptr to the protocol.
204
    uint64_t previous{};
8 ✔
205
    if (update_filter(previous, filter, event_state_))
8 ✔
206
        log.subscribe_events(BIND_WEAK(handle_event, _1, _2, _3, _4));
6 ✔
207

208
    send_json({ { "previous", previous } }, 42);
8 ✔
209
    return true;
8 ✔
210
}
211

212
// Event handlers.
213
// ----------------------------------------------------------------------------
214

215
bool protocol_admin::handle_log(const code& ec, uint8_t level, time_t zulu,
6 ✔
216
    const std::string& message) NOEXCEPT
217
{
218
    if (stopped(ec))
6 ✔
219
        return false;
220

221
    if (websocket())
5 ✔
222
    {
223
        POST(do_log, level, zulu * 1'000_u64, message);
4 ✔
224
    }
225

226
    return true;
227
}
228

229
bool protocol_admin::handle_event(const code& ec, uint8_t event_,
4 ✔
230
    uint64_t value, const logger::time& point) NOEXCEPT
231
{
232
    bool filtered{};
4 ✔
233
    if (stopped(ec) || !get_filter(filtered, event_, event_state_))
4 ✔
234
        return false;
1 ✔
235

236
    if (!filtered || !websocket())
3 ✔
237
        return true;
1 ✔
238

239
    POST(do_event, event_, to_zulu(point), value);
2 ✔
240
    return true;
2 ✔
241
}
242

243
// Notification event handlers.
244
// ----------------------------------------------------------------------------
245
// Posted to the protocol strand.
246

247
void protocol_admin::do_log(uint8_t level, uint64_t zulu,
4 ✔
248
    const std::string& message) NOEXCEPT
249
{
250
    BC_ASSERT(stranded() && websocket());
4 ✔
251

252
    if (stopped())
4 ✔
253
        return;
254

255
    notify_json(
8 ✔
256
    {
257
        { "level", level },
258
        { "time", zulu },
259
        { "message", message }
260
    }, message.size() + 64u);
4 ✔
261
}
262

263
void protocol_admin::do_event(uint8_t event_, uint64_t zulu,
2 ✔
264
    uint64_t value_) NOEXCEPT
265
{
266
    BC_ASSERT(stranded() && websocket());
2 ✔
267

268
    if (stopped())
2 ✔
269
        return;
270

271
    notify_json(
2 ✔
272
    {
273
        { "event", event_ },
274
        { "value", value_ },
275
        { "time", zulu }
276
    }, 64);
277
}
278

279
// Utils.
280
// ----------------------------------------------------------------------------
281
// static/private
282

283
// zulu time in milliseconds since epoch (native js time format).
284
uint64_t protocol_admin::to_zulu(const logger::time& point) NOEXCEPT
2 ✔
285
{
286
    using namespace std::chrono;
287
    const auto now = logger::now();
2 ✔
288
    const auto delta = duration_cast<system_clock::duration>(now - point);
2 ✔
289
    const auto time = (system_clock::now() - delta).time_since_epoch();
2 ✔
290
    return duration_cast<milliseconds>(time).count();
2 ✔
291
}
292

293
// Unsubscribe if false.
294
bool protocol_admin::get_filter(bool& filtered, size_t bit,
4 ✔
295
    filter_t& filter) NOEXCEPT
296
{
297
    auto state = filter.load(relaxed);
298
    const auto next = set_left(state, zero, false);
4 ✔
299

300
    // Empty filter unregisters, failure implies a new filter arrived.
301
    if (is_zero(next) && filter.compare_exchange_strong(state, next, relaxed))
5 ✔
302
        return false;
303

304
    filtered = get_right(state, bit);
3 ✔
305
    return true;
3 ✔
306
}
307

308
// Subscribe if true.
309
bool protocol_admin::update_filter(uint64_t& prior, uint64_t value,
8 ✔
310
    filter_t& filter) NOEXCEPT
311
{
312
    const auto next = set_left(value, zero, true);
8 ✔
313
    const auto state = is_zero(value) ? filter.fetch_and(next, relaxed) :
8 ✔
314
        filter.exchange(next, relaxed);
315

316
    prior = set_left(state, zero, false);
8 ✔
317
    return !is_zero(value) && !get_left(state);
8 ✔
318
}
319

320
} // namespace server
321
} // 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