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

libbitcoin / libbitcoin-system / 18844083168

27 Oct 2025 02:15PM UTC coverage: 80.949% (+0.003%) from 80.946%
18844083168

push

github

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

Clean up exception trapping in address utils.

8 of 13 new or added lines in 2 files covered. (61.54%)

10593 of 13086 relevant lines covered (80.95%)

3618517.59 hits per line

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

90.09
/src/config/utilities.cpp
1
/**
2
 * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
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/system/config/utilities.hpp>
20

21
#include <regex>
22
#include <bitcoin/system/define.hpp>
23
#include <bitcoin/system/serial/serial.hpp>
24

25
namespace libbitcoin {
26
namespace system {
27
namespace config {
28

29
static_assert(array_count<boost::asio::ip::address_v4::bytes_type> == ipv4_size);
30
static_assert(array_count<boost::asio::ip::address_v6::bytes_type> == ipv6_size);
31

32
// Because deserialize doesn't convert empty to zero.
33
template <typename Integer>
34
inline bool to_integer(Integer& out, const std::string& in) NOEXCEPT
337✔
35
{
36
    if (in.empty())
337✔
37
    {
38
        out = Integer{};
141✔
39
        return true;
141✔
40
    }
41

42
    return deserialize(out, in);
196✔
43
}
44

45
// For calling consistency.
46
inline bool to_string(std::string& to, std::string&& from) NOEXCEPT
95✔
47
{
48
    to = std::move(from);
95✔
49
    return true;
95✔
50
}
51

52
// ASIO make_address allows a port on win32 (which is then lost), so guard in
53
// regex. ASIO addresses do not have ports, that's what endpoints are for.
54
inline bool make_address(asio::address& ip, const std::string& host) NOEXCEPT
147✔
55
{
56
    try
147✔
57
    {
58
        // Regex extracts literal host, non-win32 boost make_address rejects.
59
        ip = boost::asio::ip::make_address(trim_copy(host, { "[", "]" }));
294✔
60
        return true;
145✔
61
    }
62
    catch (...)
2✔
63
    {
64
        return false;
2✔
65
    }
2✔
66
}
67

68
// regex parsers.
69
// ----------------------------------------------------------------------------
70

71
// en.wikipedia.org/wiki/List_of_URI_schemes
72
// Schemes of p2p network and our zeromq endpoints.
73
#define SCHEME "(tcp|udp|http|https|inproc):\\/\\/"
74
#define IPV4   "([0-9.]+)"
75
#define IPV6   "\\[([0-9a-f:]+)\\]"
76
#define HOST   "([^:?/\\\\]+)"
77
#define PORT   ":([1-9][0-9]{0,4})"
78
#define CIDR   "\\/([1-9][0-9]{0,2})"
79

80
// sregex_iterator doesn't provide `at()`.
81
BC_PUSH_WARNING(NO_ARRAY_INDEXING)
82
BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
83

84
// Excludes ipv4 mapped/compat, unbracketed, and ports allowed by make_address.
85
bool parse_host(asio::address& ip, const std::string& value) NOEXCEPT
14✔
86
{
87
    static const std::regex regular
14✔
88
    {
89
        "^(" IPV4 "|" IPV6 ")$"
90
    };
14✔
91

92
    std::sregex_iterator token{ value.begin(), value.end(), regular };
14✔
93
    std::sregex_iterator end{};
14✔
94
    return token != end
14✔
95
        && make_address(ip, (*token)[1].str());
20✔
96
}
97

98
// Excludes ipv4 mapped/compat to ipv6.
99
bool parse_authority(asio::address& ip, uint16_t& port, uint8_t& cidr,
158✔
100
    const std::string& value) NOEXCEPT
101
{
102
    constexpr uint8_t maximum_cidr_ip4 = 32;
158✔
103
    constexpr uint8_t maximum_cidr_ip6 = 128;
158✔
104
    static const std::regex regular
158✔
105
    {
106
        "^(" IPV4 "|" IPV6 ")(" PORT ")?(" CIDR ")?$"
107
    };
158✔
108

109
    std::sregex_iterator token{ value.begin(), value.end(), regular };
158✔
110
    std::sregex_iterator end{};
158✔
111
    return token != end
158✔
112
        && make_address(ip, (*token)[1].str())
299✔
113
        && to_integer(port, (*token)[5].str())
297✔
114
        && to_integer(cidr, (*token)[7].str())
432✔
115
        && ((ip.is_v4() && cidr <= maximum_cidr_ip4) ||
295✔
116
            (ip.is_v6() && cidr <= maximum_cidr_ip6));
67✔
117
}
118

119
// Excludes ipv4 mapped/compat to ipv6.
120
bool parse_url(std::string& scheme, std::string& host, uint16_t& port,
46✔
121
    const std::string& value) NOEXCEPT
122
{
123
    static const std::regex regular
46✔
124
    {
125
        "^(" SCHEME ")?(" IPV4 "|" IPV6 "|" HOST ")(" PORT ")?$"
126
    };
46✔
127

128
    std::sregex_iterator token{ value.begin(), value.end(), regular };
46✔
129
    std::sregex_iterator end{};
46✔
130
    return token != end
46✔
131
        && to_string(scheme, (*token)[2].str())
80✔
132
        && to_string(host,   (*token)[3].str())
80✔
133
        && to_integer(port,  (*token)[8].str());
114✔
134
}
135

136
// Excludes ipv4 mapped/compat to ipv6.
137
bool parse_endpoint(std::string& host, uint16_t& port,
33✔
138
    const std::string& value) NOEXCEPT
139
{
140
    static const std::regex regular
33✔
141
    {
142
        "^(" IPV4 "|" IPV6 "|" HOST ")(" PORT ")?$"
143
    };
33✔
144

145
    std::sregex_iterator token{ value.begin(), value.end(), regular };
33✔
146
    std::sregex_iterator end{};
33✔
147
    return token != end
33✔
148
        && to_string(host, (*token)[1].str())
60✔
149
        && to_integer(port, (*token)[6].str());
87✔
150
}
151

152
BC_POP_WARNING()
153
BC_POP_WARNING()
154

155
// asio/asio conversions.
156
// ----------------------------------------------------------------------------
157

158
// address_v6.to_v4 removed in boost 1.87, no replacement.
159
inline asio::address to_v4(const boost::asio::ip::address_v6& ip6) THROWS
26✔
160
{
161
    // Required for equivalence with boost 1.86.
162
    if (!ip6.is_v4_mapped())
26✔
163
    {
164
        using namespace boost::asio::detail;
25✔
165
        throw_exception(boost::asio::ip::bad_address_cast{});
25✔
166
    }
167

168
    const auto bytes = ip6.to_bytes();
1✔
169
    return
1✔
170
    {
171
        boost::asio::ip::address_v4
2✔
172
        {
173
            boost::asio::ip::address_v4::bytes_type
2✔
174
            {
175
                {
176
                    bytes.at(12), bytes.at(13), bytes.at(14), bytes.at(15)
1✔
177
                }
178
            }
179
        } 
180
    };
1✔
181
}
182

183
// Convert IPv6-mapped to IPV4 (ensures consistent internal matching).
184
// Reduce 4 encodings (IPv6, IPv6-mapped, IPv6-compat, IPv4) to 2 (IPv6, IPv4).
185
asio::address denormalize(const asio::address& ip) NOEXCEPT
37✔
186
{
187
    if (ip.is_v6())
37✔
188
    {
189
        try
26✔
190
        {
191
            return { to_v4(ip.to_v6()) };
26✔
192
        }
193
        catch (...)
25✔
194
        {
195
            return ip;
25✔
196
        }
25✔
197
    }
198

199
    return ip;
11✔
200
}
201

202
// asio/string host conversions.
203
// ----------------------------------------------------------------------------
204

205
inline std::string to_host(const boost::asio::ip::address_v6& ip6) NOEXCEPT
13✔
206
{
207
    try
13✔
208
    {
209
        BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
210
        return ip6.is_v4_mapped() ? to_host(to_v4(ip6)) : ip6.to_string();
13✔
211
        BC_POP_WARNING()
212
    }
NEW
213
    catch (...)
×
214
    {
215
        return { "::" };
×
216
    }
×
217
}
218

219
inline std::string to_host(const boost::asio::ip::address_v4& ip4) NOEXCEPT
10✔
220
{
221
    try
10✔
222
    {
223
        return ip4.to_string();
10✔
224
    }
NEW
225
    catch (...)
×
226
    {
227
        return { "0.0.0.0" };
×
228
    }
×
229
}
230

231
// Serialize to host denormal form (unmapped) without ipv6 backets.
232
std::string to_host(const asio::address& ip) NOEXCEPT
23✔
233
{
234
    try
23✔
235
    {
236
        const auto host = denormalize(ip);
23✔
237
        BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
238
        return host.is_v4() ? to_host(host.to_v4()) : to_host(host.to_v6());
23✔
239
        BC_POP_WARNING()
240
    }
NEW
241
    catch (...)
×
242
    {
243
        return { "0.0.0.0" };
×
244
    }
×
245
}
246

247
// Serialize to host denormal form (unmapped) and with ipv6 backets.
248
std::string to_literal(const asio::address& ip) NOEXCEPT
17✔
249
{
250
    const auto host = to_host(ip);
17✔
251
    return (host.find(":") == std::string::npos) ? host : ("[" + host + "]");
27✔
252
}
253

254
// Rejects ipv6 mapped/compat to ipv4 and unbracketed ipv6.
255
asio::address from_host(const std::string& host) THROWS
14✔
256
{
257
    asio::address out{};
14✔
258
    if (!parse_host(out, host))
14✔
259
        throw istream_exception{ host };
8✔
260

261
    return out;
6✔
262
}
263

264
// Member if subnet addresses contain host.
265
// ----------------------------------------------------------------------------
266

267
inline bool is_member_v4(const boost::asio::ip::address_v4& ip4,
28✔
268
    const boost::asio::ip::address_v4& net4, uint8_t cidr)  THROWS
269
{
270
    const auto hosts = boost::asio::ip::make_network_v4(net4, cidr).hosts();
28✔
271
    return hosts.find(ip4) != hosts.end();
28✔
272
}
273

274
inline bool is_member_v6(const boost::asio::ip::address_v6& ip6,
43✔
275
    const boost::asio::ip::address_v6& net6, uint8_t cidr) THROWS
276
{
277
    const auto hosts = boost::asio::ip::make_network_v6(net6, cidr).hosts();
43✔
278
    return hosts.find(ip6) != hosts.end();
43✔
279
}
280

281
// This assumes host and/or subnet v4 address(s) unmapped.
282
bool is_member(const asio::address& ip, const asio::address& subnet,
71✔
283
    uint8_t cidr) NOEXCEPT
284
{
285
    try
71✔
286
    {
287
        if (ip.is_v4() && subnet.is_v4())
71✔
288
            return is_member_v4(ip.to_v4(), subnet.to_v4(), cidr);
28✔
289

290
        if (ip.is_v6() && subnet.is_v6())
43✔
291
            return is_member_v6(ip.to_v6(), subnet.to_v6(), cidr);
43✔
292
    }
NEW
293
    catch (...)
×
294
    {
295
    }
×
296

297
    return false;
298
}
299

300
} // namespace config
301
} // namespace system
302
} // namespace libbitcoin
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc