1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/http
7  
// Official repository: https://github.com/cppalliance/http
8  
//
8  
//
9  

9  

10  
/** @file
10  
/** @file
11  
    bcrypt password hashing library.
11  
    bcrypt password hashing library.
12  

12  

13  
    This header provides bcrypt password hashing with three API tiers:
13  
    This header provides bcrypt password hashing with three API tiers:
14  

14  

15  
    **Tier 1 -- Synchronous** (low-level, no capy dependency):
15  
    **Tier 1 -- Synchronous** (low-level, no capy dependency):
16  
    @code
16  
    @code
17  
    bcrypt::result r = bcrypt::hash("password", 12);
17  
    bcrypt::result r = bcrypt::hash("password", 12);
18  
    system::error_code ec;
18  
    system::error_code ec;
19  
    bool ok = bcrypt::compare("password", r.str(), ec);
19  
    bool ok = bcrypt::compare("password", r.str(), ec);
20  
    @endcode
20  
    @endcode
21  

21  

22  
    **Tier 2 -- Capy Task** (lazy coroutine, caller controls executor):
22  
    **Tier 2 -- Capy Task** (lazy coroutine, caller controls executor):
23  
    @code
23  
    @code
24  
    auto r = co_await bcrypt::hash_task("password", 12);
24  
    auto r = co_await bcrypt::hash_task("password", 12);
25  
    @endcode
25  
    @endcode
26  

26  

27  
    **Tier 3 -- Friendly Async** (auto-offloads to system thread pool):
27  
    **Tier 3 -- Friendly Async** (auto-offloads to system thread pool):
28  
    @code
28  
    @code
29  
    auto r = co_await bcrypt::hash_async("password", 12);
29  
    auto r = co_await bcrypt::hash_async("password", 12);
30  
    bool ok = co_await bcrypt::compare_async("password", r.str());
30  
    bool ok = co_await bcrypt::compare_async("password", r.str());
31  
    @endcode
31  
    @endcode
32  
*/
32  
*/
33  

33  

34  
#ifndef BOOST_HTTP_BCRYPT_HPP
34  
#ifndef BOOST_HTTP_BCRYPT_HPP
35  
#define BOOST_HTTP_BCRYPT_HPP
35  
#define BOOST_HTTP_BCRYPT_HPP
36  

36  

37  
#include <boost/http/detail/config.hpp>
37  
#include <boost/http/detail/config.hpp>
38  
#include <boost/http/detail/except.hpp>
38  
#include <boost/http/detail/except.hpp>
39  
#include <boost/core/detail/string_view.hpp>
39  
#include <boost/core/detail/string_view.hpp>
40  
#include <boost/system/error_category.hpp>
40  
#include <boost/system/error_category.hpp>
41  
#include <boost/system/error_code.hpp>
41  
#include <boost/system/error_code.hpp>
42  
#include <boost/system/is_error_code_enum.hpp>
42  
#include <boost/system/is_error_code_enum.hpp>
43  

43  

44  
#include <boost/capy/coro.hpp>
44  
#include <boost/capy/coro.hpp>
45  
#include <boost/capy/task.hpp>
45  
#include <boost/capy/task.hpp>
46  
#include <boost/capy/ex/executor_ref.hpp>
46  
#include <boost/capy/ex/executor_ref.hpp>
47  
#include <boost/capy/ex/run_async.hpp>
47  
#include <boost/capy/ex/run_async.hpp>
48  
#include <boost/capy/ex/system_context.hpp>
48  
#include <boost/capy/ex/system_context.hpp>
49  

49  

50  
#include <cstddef>
50  
#include <cstddef>
51  
#include <cstring>
51  
#include <cstring>
52  
#include <exception>
52  
#include <exception>
53  
#include <stop_token>
53  
#include <stop_token>
54  
#include <string>
54  
#include <string>
55  
#include <system_error>
55  
#include <system_error>
56  

56  

57  
namespace boost {
57  
namespace boost {
58  
namespace http {
58  
namespace http {
59  
namespace bcrypt {
59  
namespace bcrypt {
60  

60  

61  
//------------------------------------------------
61  
//------------------------------------------------
62  

62  

63  
/** bcrypt hash version prefix.
63  
/** bcrypt hash version prefix.
64  

64  

65  
    The version determines which variant of bcrypt is used.
65  
    The version determines which variant of bcrypt is used.
66  
    All versions produce compatible hashes.
66  
    All versions produce compatible hashes.
67  
*/
67  
*/
68  
enum class version
68  
enum class version
69  
{
69  
{
70  
    /// $2a$ - Original specification
70  
    /// $2a$ - Original specification
71  
    v2a,
71  
    v2a,
72  

72  

73  
    /// $2b$ - Fixed handling of passwords > 255 chars (recommended)
73  
    /// $2b$ - Fixed handling of passwords > 255 chars (recommended)
74  
    v2b
74  
    v2b
75  
};
75  
};
76  

76  

77  
//------------------------------------------------
77  
//------------------------------------------------
78  

78  

79  
/** Error codes for bcrypt operations.
79  
/** Error codes for bcrypt operations.
80  

80  

81  
    These errors indicate malformed input from untrusted sources.
81  
    These errors indicate malformed input from untrusted sources.
82  
*/
82  
*/
83  
enum class error
83  
enum class error
84  
{
84  
{
85  
    /// Success
85  
    /// Success
86  
    ok = 0,
86  
    ok = 0,
87  

87  

88  
    /// Salt string is malformed
88  
    /// Salt string is malformed
89  
    invalid_salt,
89  
    invalid_salt,
90  

90  

91  
    /// Hash string is malformed
91  
    /// Hash string is malformed
92  
    invalid_hash
92  
    invalid_hash
93  
};
93  
};
94  

94  

95  
} // bcrypt
95  
} // bcrypt
96  
} // http
96  
} // http
97  

97  

98  
namespace system {
98  
namespace system {
99  
template<>
99  
template<>
100  
struct is_error_code_enum<
100  
struct is_error_code_enum<
101  
    ::boost::http::bcrypt::error>
101  
    ::boost::http::bcrypt::error>
102  
{
102  
{
103  
    static bool const value = true;
103  
    static bool const value = true;
104  
};
104  
};
105  
} // system
105  
} // system
106  
} // boost
106  
} // boost
107  

107  

108  
namespace std {
108  
namespace std {
109  
template<>
109  
template<>
110  
struct is_error_code_enum<
110  
struct is_error_code_enum<
111  
    ::boost::http::bcrypt::error>
111  
    ::boost::http::bcrypt::error>
112  
    : std::true_type {};
112  
    : std::true_type {};
113  
} // std
113  
} // std
114  

114  

115  
namespace boost {
115  
namespace boost {
116  
namespace http {
116  
namespace http {
117  
namespace bcrypt {
117  
namespace bcrypt {
118  

118  

119  
namespace detail {
119  
namespace detail {
120  

120  

121  
struct BOOST_SYMBOL_VISIBLE
121  
struct BOOST_SYMBOL_VISIBLE
122  
    error_cat_type
122  
    error_cat_type
123  
    : system::error_category
123  
    : system::error_category
124  
{
124  
{
125  
    BOOST_HTTP_DECL const char* name(
125  
    BOOST_HTTP_DECL const char* name(
126  
        ) const noexcept override;
126  
        ) const noexcept override;
127  
    BOOST_HTTP_DECL std::string message(
127  
    BOOST_HTTP_DECL std::string message(
128  
        int) const override;
128  
        int) const override;
129  
    BOOST_HTTP_DECL char const* message(
129  
    BOOST_HTTP_DECL char const* message(
130  
        int, char*, std::size_t
130  
        int, char*, std::size_t
131  
            ) const noexcept override;
131  
            ) const noexcept override;
132  
    BOOST_SYSTEM_CONSTEXPR error_cat_type()
132  
    BOOST_SYSTEM_CONSTEXPR error_cat_type()
133  
        : error_category(0xbc8f2a4e7c193d56)
133  
        : error_category(0xbc8f2a4e7c193d56)
134  
    {
134  
    {
135  
    }
135  
    }
136  
};
136  
};
137  

137  

138  
BOOST_HTTP_DECL extern
138  
BOOST_HTTP_DECL extern
139  
    error_cat_type error_cat;
139  
    error_cat_type error_cat;
140  

140  

141  
} // detail
141  
} // detail
142  

142  

143  
inline
143  
inline
144  
BOOST_SYSTEM_CONSTEXPR
144  
BOOST_SYSTEM_CONSTEXPR
145  
system::error_code
145  
system::error_code
146  
make_error_code(
146  
make_error_code(
147  
    error ev) noexcept
147  
    error ev) noexcept
148  
{
148  
{
149  
    return system::error_code{
149  
    return system::error_code{
150  
        static_cast<std::underlying_type<
150  
        static_cast<std::underlying_type<
151  
            error>::type>(ev),
151  
            error>::type>(ev),
152  
        detail::error_cat};
152  
        detail::error_cat};
153  
}
153  
}
154  

154  

155  
//------------------------------------------------
155  
//------------------------------------------------
156  

156  

157  
/** Fixed-size buffer for bcrypt hash output.
157  
/** Fixed-size buffer for bcrypt hash output.
158  

158  

159  
    Stores a bcrypt hash string (max 60 chars) in an
159  
    Stores a bcrypt hash string (max 60 chars) in an
160  
    inline buffer with no heap allocation.
160  
    inline buffer with no heap allocation.
161  

161  

162  
    @par Example
162  
    @par Example
163  
    @code
163  
    @code
164  
    bcrypt::result r = bcrypt::hash("password", 10);
164  
    bcrypt::result r = bcrypt::hash("password", 10);
165  
    core::string_view sv = r;  // or r.str()
165  
    core::string_view sv = r;  // or r.str()
166  
    std::cout << r.c_str();    // null-terminated
166  
    std::cout << r.c_str();    // null-terminated
167  
    @endcode
167  
    @endcode
168  
*/
168  
*/
169  
class result
169  
class result
170  
{
170  
{
171  
    char buf_[61];
171  
    char buf_[61];
172  
    unsigned char size_;
172  
    unsigned char size_;
173  

173  

174  
public:
174  
public:
175  
    /** Default constructor.
175  
    /** Default constructor.
176  

176  

177  
        Constructs an empty result.
177  
        Constructs an empty result.
178  
    */
178  
    */
179  
    result() noexcept
179  
    result() noexcept
180  
        : size_(0)
180  
        : size_(0)
181  
    {
181  
    {
182  
        buf_[0] = '\0';
182  
        buf_[0] = '\0';
183  
    }
183  
    }
184  

184  

185  
    /** Return the hash as a string_view.
185  
    /** Return the hash as a string_view.
186  
    */
186  
    */
187  
    core::string_view
187  
    core::string_view
188  
    str() const noexcept
188  
    str() const noexcept
189  
    {
189  
    {
190  
        return core::string_view(buf_, size_);
190  
        return core::string_view(buf_, size_);
191  
    }
191  
    }
192  

192  

193  
    /** Implicit conversion to string_view.
193  
    /** Implicit conversion to string_view.
194  
    */
194  
    */
195  
    operator core::string_view() const noexcept
195  
    operator core::string_view() const noexcept
196  
    {
196  
    {
197  
        return str();
197  
        return str();
198  
    }
198  
    }
199  

199  

200  
    /** Return null-terminated C string.
200  
    /** Return null-terminated C string.
201  
    */
201  
    */
202  
    char const*
202  
    char const*
203  
    c_str() const noexcept
203  
    c_str() const noexcept
204  
    {
204  
    {
205  
        return buf_;
205  
        return buf_;
206  
    }
206  
    }
207  

207  

208  
    /** Return pointer to data.
208  
    /** Return pointer to data.
209  
    */
209  
    */
210  
    char const*
210  
    char const*
211  
    data() const noexcept
211  
    data() const noexcept
212  
    {
212  
    {
213  
        return buf_;
213  
        return buf_;
214  
    }
214  
    }
215  

215  

216  
    /** Return size in bytes (excludes null terminator).
216  
    /** Return size in bytes (excludes null terminator).
217  
    */
217  
    */
218  
    std::size_t
218  
    std::size_t
219  
    size() const noexcept
219  
    size() const noexcept
220  
    {
220  
    {
221  
        return size_;
221  
        return size_;
222  
    }
222  
    }
223  

223  

224  
    /** Check if result is empty.
224  
    /** Check if result is empty.
225  
    */
225  
    */
226  
    bool
226  
    bool
227  
    empty() const noexcept
227  
    empty() const noexcept
228  
    {
228  
    {
229  
        return size_ == 0;
229  
        return size_ == 0;
230  
    }
230  
    }
231  

231  

232  
    /** Check if result contains valid data.
232  
    /** Check if result contains valid data.
233  
    */
233  
    */
234  
    explicit
234  
    explicit
235  
    operator bool() const noexcept
235  
    operator bool() const noexcept
236  
    {
236  
    {
237  
        return size_ != 0;
237  
        return size_ != 0;
238  
    }
238  
    }
239  

239  

240  
private:
240  
private:
241  
    friend BOOST_HTTP_DECL result gen_salt(unsigned, version);
241  
    friend BOOST_HTTP_DECL result gen_salt(unsigned, version);
242  
    friend BOOST_HTTP_DECL result hash(core::string_view, unsigned, version);
242  
    friend BOOST_HTTP_DECL result hash(core::string_view, unsigned, version);
243  
    friend BOOST_HTTP_DECL result hash(core::string_view, core::string_view, system::error_code&);
243  
    friend BOOST_HTTP_DECL result hash(core::string_view, core::string_view, system::error_code&);
244  

244  

245  
    char* buf() noexcept { return buf_; }
245  
    char* buf() noexcept { return buf_; }
246  
    void set_size(unsigned char n) noexcept
246  
    void set_size(unsigned char n) noexcept
247  
    {
247  
    {
248  
        size_ = n;
248  
        size_ = n;
249  
        buf_[n] = '\0';
249  
        buf_[n] = '\0';
250  
    }
250  
    }
251  
};
251  
};
252  

252  

253  
//------------------------------------------------
253  
//------------------------------------------------
254  

254  

255  
/** Generate a random salt.
255  
/** Generate a random salt.
256  

256  

257  
    Creates a bcrypt salt string suitable for use with
257  
    Creates a bcrypt salt string suitable for use with
258  
    the hash() function.
258  
    the hash() function.
259  

259  

260  
    @par Preconditions
260  
    @par Preconditions
261  
    @code
261  
    @code
262  
    rounds >= 4 && rounds <= 31
262  
    rounds >= 4 && rounds <= 31
263  
    @endcode
263  
    @endcode
264  

264  

265  
    @par Exception Safety
265  
    @par Exception Safety
266  
    Strong guarantee.
266  
    Strong guarantee.
267  

267  

268  
    @par Complexity
268  
    @par Complexity
269  
    Constant.
269  
    Constant.
270  

270  

271  
    @param rounds Cost factor. Each increment doubles the work.
271  
    @param rounds Cost factor. Each increment doubles the work.
272  
    Default is 10, which takes approximately 100ms on modern hardware.
272  
    Default is 10, which takes approximately 100ms on modern hardware.
273  

273  

274  
    @param ver Hash version to use.
274  
    @param ver Hash version to use.
275  

275  

276  
    @return A 29-character salt string.
276  
    @return A 29-character salt string.
277  

277  

278  
    @throws std::invalid_argument if rounds is out of range.
278  
    @throws std::invalid_argument if rounds is out of range.
279  
    @throws system_error on RNG failure.
279  
    @throws system_error on RNG failure.
280  
*/
280  
*/
281  
BOOST_HTTP_DECL
281  
BOOST_HTTP_DECL
282  
result
282  
result
283  
gen_salt(
283  
gen_salt(
284  
    unsigned rounds = 10,
284  
    unsigned rounds = 10,
285  
    version ver = version::v2b);
285  
    version ver = version::v2b);
286  

286  

287  
/** Hash a password with auto-generated salt.
287  
/** Hash a password with auto-generated salt.
288  

288  

289  
    Generates a random salt and hashes the password.
289  
    Generates a random salt and hashes the password.
290  

290  

291  
    @par Preconditions
291  
    @par Preconditions
292  
    @code
292  
    @code
293  
    rounds >= 4 && rounds <= 31
293  
    rounds >= 4 && rounds <= 31
294  
    @endcode
294  
    @endcode
295  

295  

296  
    @par Exception Safety
296  
    @par Exception Safety
297  
    Strong guarantee.
297  
    Strong guarantee.
298  

298  

299  
    @par Complexity
299  
    @par Complexity
300  
    O(2^rounds).
300  
    O(2^rounds).
301  

301  

302  
    @param password The password to hash. Only the first 72 bytes
302  
    @param password The password to hash. Only the first 72 bytes
303  
    are used (bcrypt limitation).
303  
    are used (bcrypt limitation).
304  

304  

305  
    @param rounds Cost factor. Each increment doubles the work.
305  
    @param rounds Cost factor. Each increment doubles the work.
306  

306  

307  
    @param ver Hash version to use.
307  
    @param ver Hash version to use.
308  

308  

309  
    @return A 60-character hash string.
309  
    @return A 60-character hash string.
310  

310  

311  
    @throws std::invalid_argument if rounds is out of range.
311  
    @throws std::invalid_argument if rounds is out of range.
312  
    @throws system_error on RNG failure.
312  
    @throws system_error on RNG failure.
313  
*/
313  
*/
314  
BOOST_HTTP_DECL
314  
BOOST_HTTP_DECL
315  
result
315  
result
316  
hash(
316  
hash(
317  
    core::string_view password,
317  
    core::string_view password,
318  
    unsigned rounds = 10,
318  
    unsigned rounds = 10,
319  
    version ver = version::v2b);
319  
    version ver = version::v2b);
320  

320  

321  
/** Hash a password using a provided salt.
321  
/** Hash a password using a provided salt.
322  

322  

323  
    Uses the given salt to hash the password. The salt should
323  
    Uses the given salt to hash the password. The salt should
324  
    be a string previously returned by gen_salt() or extracted
324  
    be a string previously returned by gen_salt() or extracted
325  
    from a hash string.
325  
    from a hash string.
326  

326  

327  
    @par Exception Safety
327  
    @par Exception Safety
328  
    Strong guarantee.
328  
    Strong guarantee.
329  

329  

330  
    @par Complexity
330  
    @par Complexity
331  
    O(2^rounds).
331  
    O(2^rounds).
332  

332  

333  
    @param password The password to hash.
333  
    @param password The password to hash.
334  

334  

335  
    @param salt The salt string (29 characters).
335  
    @param salt The salt string (29 characters).
336  

336  

337  
    @param ec Set to bcrypt::error::invalid_salt if the salt
337  
    @param ec Set to bcrypt::error::invalid_salt if the salt
338  
    is malformed.
338  
    is malformed.
339  

339  

340  
    @return A 60-character hash string, or empty result on error.
340  
    @return A 60-character hash string, or empty result on error.
341  
*/
341  
*/
342  
BOOST_HTTP_DECL
342  
BOOST_HTTP_DECL
343  
result
343  
result
344  
hash(
344  
hash(
345  
    core::string_view password,
345  
    core::string_view password,
346  
    core::string_view salt,
346  
    core::string_view salt,
347  
    system::error_code& ec);
347  
    system::error_code& ec);
348  

348  

349  
/** Compare a password against a hash.
349  
/** Compare a password against a hash.
350  

350  

351  
    Extracts the salt from the hash, re-hashes the password,
351  
    Extracts the salt from the hash, re-hashes the password,
352  
    and compares the result.
352  
    and compares the result.
353  

353  

354  
    @par Exception Safety
354  
    @par Exception Safety
355  
    Strong guarantee.
355  
    Strong guarantee.
356  

356  

357  
    @par Complexity
357  
    @par Complexity
358  
    O(2^rounds).
358  
    O(2^rounds).
359  

359  

360  
    @param password The plaintext password to check.
360  
    @param password The plaintext password to check.
361  

361  

362  
    @param hash The hash string to compare against.
362  
    @param hash The hash string to compare against.
363  

363  

364  
    @param ec Set to bcrypt::error::invalid_hash if the hash
364  
    @param ec Set to bcrypt::error::invalid_hash if the hash
365  
    is malformed.
365  
    is malformed.
366  

366  

367  
    @return true if the password matches the hash, false if
367  
    @return true if the password matches the hash, false if
368  
    it does not match OR if an error occurred. Always check
368  
    it does not match OR if an error occurred. Always check
369  
    ec to distinguish between a mismatch and an error.
369  
    ec to distinguish between a mismatch and an error.
370  
*/
370  
*/
371  
BOOST_HTTP_DECL
371  
BOOST_HTTP_DECL
372  
bool
372  
bool
373  
compare(
373  
compare(
374  
    core::string_view password,
374  
    core::string_view password,
375  
    core::string_view hash,
375  
    core::string_view hash,
376  
    system::error_code& ec);
376  
    system::error_code& ec);
377  

377  

378  
/** Extract the cost factor from a hash string.
378  
/** Extract the cost factor from a hash string.
379  

379  

380  
    @par Exception Safety
380  
    @par Exception Safety
381  
    Strong guarantee.
381  
    Strong guarantee.
382  

382  

383  
    @par Complexity
383  
    @par Complexity
384  
    Constant.
384  
    Constant.
385  

385  

386  
    @param hash The hash string to parse.
386  
    @param hash The hash string to parse.
387  

387  

388  
    @param ec Set to bcrypt::error::invalid_hash if the hash
388  
    @param ec Set to bcrypt::error::invalid_hash if the hash
389  
    is malformed.
389  
    is malformed.
390  

390  

391  
    @return The cost factor (4-31) on success, or 0 if an
391  
    @return The cost factor (4-31) on success, or 0 if an
392  
    error occurred.
392  
    error occurred.
393  
*/
393  
*/
394  
BOOST_HTTP_DECL
394  
BOOST_HTTP_DECL
395  
unsigned
395  
unsigned
396  
get_rounds(
396  
get_rounds(
397  
    core::string_view hash,
397  
    core::string_view hash,
398  
    system::error_code& ec);
398  
    system::error_code& ec);
399  

399  

400  
namespace detail {
400  
namespace detail {
401  

401  

402  
// bcrypt truncates passwords to 72 bytes
402  
// bcrypt truncates passwords to 72 bytes
403  
struct password_buf
403  
struct password_buf
404  
{
404  
{
405  
    char data_[72];
405  
    char data_[72];
406  
    unsigned char size_;
406  
    unsigned char size_;
407  

407  

408  
    explicit password_buf(
408  
    explicit password_buf(
409  
        core::string_view s) noexcept
409  
        core::string_view s) noexcept
410  
        : size_(static_cast<unsigned char>(
410  
        : size_(static_cast<unsigned char>(
411  
            (std::min)(s.size(), std::size_t{72})))
411  
            (std::min)(s.size(), std::size_t{72})))
412  
    {
412  
    {
413  
        std::memcpy(data_, s.data(), size_);
413  
        std::memcpy(data_, s.data(), size_);
414  
    }
414  
    }
415  

415  

416  
    operator core::string_view() const noexcept
416  
    operator core::string_view() const noexcept
417  
    {
417  
    {
418  
        return {data_, size_};
418  
        return {data_, size_};
419  
    }
419  
    }
420  
};
420  
};
421  

421  

422  
// bcrypt hashes are always 60 characters
422  
// bcrypt hashes are always 60 characters
423  
struct hash_buf
423  
struct hash_buf
424  
{
424  
{
425  
    char data_[61];
425  
    char data_[61];
426  
    unsigned char size_;
426  
    unsigned char size_;
427  

427  

428  
    explicit hash_buf(
428  
    explicit hash_buf(
429  
        core::string_view s) noexcept
429  
        core::string_view s) noexcept
430  
        : size_(static_cast<unsigned char>(
430  
        : size_(static_cast<unsigned char>(
431  
            (std::min)(s.size(), std::size_t{60})))
431  
            (std::min)(s.size(), std::size_t{60})))
432  
    {
432  
    {
433  
        std::memcpy(data_, s.data(), size_);
433  
        std::memcpy(data_, s.data(), size_);
434  
        data_[size_] = '\0';
434  
        data_[size_] = '\0';
435  
    }
435  
    }
436  

436  

437  
    operator core::string_view() const noexcept
437  
    operator core::string_view() const noexcept
438  
    {
438  
    {
439  
        return {data_, size_};
439  
        return {data_, size_};
440  
    }
440  
    }
441  
};
441  
};
442  

442  

443  
} // detail
443  
} // detail
444  

444  

445  
//------------------------------------------------
445  
//------------------------------------------------
446  

446  

447  
/** Hash a password, returning a lazy task.
447  
/** Hash a password, returning a lazy task.
448  

448  

449  
    Returns a @ref capy::task that wraps the synchronous
449  
    Returns a @ref capy::task that wraps the synchronous
450  
    hash() call. The caller can co_await this task directly
450  
    hash() call. The caller can co_await this task directly
451  
    or launch it on a specific executor via run_async().
451  
    or launch it on a specific executor via run_async().
452  

452  

453  
    @par Example
453  
    @par Example
454  
    @code
454  
    @code
455  
    // co_await in current context
455  
    // co_await in current context
456  
    bcrypt::result r = co_await bcrypt::hash_task("password", 12);
456  
    bcrypt::result r = co_await bcrypt::hash_task("password", 12);
457  

457  

458  
    // or launch on a specific executor
458  
    // or launch on a specific executor
459  
    run_async(my_executor)(bcrypt::hash_task("password", 12));
459  
    run_async(my_executor)(bcrypt::hash_task("password", 12));
460  
    @endcode
460  
    @endcode
461  

461  

462  
    @param password The password to hash.
462  
    @param password The password to hash.
463  

463  

464  
    @param rounds Cost factor. Each increment doubles the work.
464  
    @param rounds Cost factor. Each increment doubles the work.
465  

465  

466  
    @param ver Hash version to use.
466  
    @param ver Hash version to use.
467  

467  

468  
    @return A lazy task yielding `result`.
468  
    @return A lazy task yielding `result`.
469  

469  

470  
    @throws std::invalid_argument if rounds is out of range.
470  
    @throws std::invalid_argument if rounds is out of range.
471  
    @throws system_error on RNG failure.
471  
    @throws system_error on RNG failure.
472  
*/
472  
*/
473  
inline
473  
inline
474  
capy::task<result>
474  
capy::task<result>
475  
hash_task(
475  
hash_task(
476  
    core::string_view password,
476  
    core::string_view password,
477  
    unsigned rounds = 10,
477  
    unsigned rounds = 10,
478  
    version ver = version::v2b)
478  
    version ver = version::v2b)
479  
{
479  
{
480  
    detail::password_buf pw(password);
480  
    detail::password_buf pw(password);
481  
    co_return hash(pw, rounds, ver);
481  
    co_return hash(pw, rounds, ver);
482  
}
482  
}
483  

483  

484  
/** Compare a password against a hash, returning a lazy task.
484  
/** Compare a password against a hash, returning a lazy task.
485  

485  

486  
    Returns a @ref capy::task that wraps the synchronous
486  
    Returns a @ref capy::task that wraps the synchronous
487  
    compare() call. Errors are translated to exceptions.
487  
    compare() call. Errors are translated to exceptions.
488  

488  

489  
    @par Example
489  
    @par Example
490  
    @code
490  
    @code
491  
    bool ok = co_await bcrypt::compare_task("password", stored_hash);
491  
    bool ok = co_await bcrypt::compare_task("password", stored_hash);
492  
    @endcode
492  
    @endcode
493  

493  

494  
    @param password The plaintext password to check.
494  
    @param password The plaintext password to check.
495  

495  

496  
    @param hash_str The hash string to compare against.
496  
    @param hash_str The hash string to compare against.
497  

497  

498  
    @return A lazy task yielding `bool`.
498  
    @return A lazy task yielding `bool`.
499  

499  

500  
    @throws system_error if the hash is malformed.
500  
    @throws system_error if the hash is malformed.
501  
*/
501  
*/
502  
inline
502  
inline
503  
capy::task<bool>
503  
capy::task<bool>
504  
compare_task(
504  
compare_task(
505  
    core::string_view password,
505  
    core::string_view password,
506  
    core::string_view hash_str)
506  
    core::string_view hash_str)
507  
{
507  
{
508  
    detail::password_buf pw(password);
508  
    detail::password_buf pw(password);
509  
    detail::hash_buf hs(hash_str);
509  
    detail::hash_buf hs(hash_str);
510  
    system::error_code ec;
510  
    system::error_code ec;
511  
    bool ok = compare(pw, hs, ec);
511  
    bool ok = compare(pw, hs, ec);
512  
    if(ec.failed())
512  
    if(ec.failed())
513  
        http::detail::throw_system_error(ec);
513  
        http::detail::throw_system_error(ec);
514  
    co_return ok;
514  
    co_return ok;
515  
}
515  
}
516  

516  

517  
//------------------------------------------------
517  
//------------------------------------------------
518  

518  

519  
namespace detail {
519  
namespace detail {
520  

520  

521  
struct hash_async_op
521  
struct hash_async_op
522  
{
522  
{
523  
    password_buf password_;
523  
    password_buf password_;
524  
    unsigned rounds_;
524  
    unsigned rounds_;
525  
    version ver_;
525  
    version ver_;
526  
    result result_;
526  
    result result_;
527  
    std::exception_ptr ep_;
527  
    std::exception_ptr ep_;
528  

528  

529  
    bool await_ready() const noexcept
529  
    bool await_ready() const noexcept
530  
    {
530  
    {
531  
        return false;
531  
        return false;
532  
    }
532  
    }
533  

533  

534  
    void await_suspend(
534  
    void await_suspend(
535  
        capy::coro cont,
535  
        capy::coro cont,
536  
        capy::executor_ref caller_ex,
536  
        capy::executor_ref caller_ex,
537  
        std::stop_token)
537  
        std::stop_token)
538  
    {
538  
    {
539  
        auto& pool = capy::get_system_context();
539  
        auto& pool = capy::get_system_context();
540  
        auto sys_ex = pool.get_executor();
540  
        auto sys_ex = pool.get_executor();
541  
        capy::run_async(sys_ex,
541  
        capy::run_async(sys_ex,
542  
            [this, cont, caller_ex]
542  
            [this, cont, caller_ex]
543  
            (result r) mutable
543  
            (result r) mutable
544  
            {
544  
            {
545  
                result_ = r;
545  
                result_ = r;
546  
                caller_ex.dispatch(cont);
546  
                caller_ex.dispatch(cont);
547  
            },
547  
            },
548  
            [this, cont, caller_ex]
548  
            [this, cont, caller_ex]
549  
            (std::exception_ptr ep) mutable
549  
            (std::exception_ptr ep) mutable
550  
            {
550  
            {
551  
                ep_ = ep;
551  
                ep_ = ep;
552  
                caller_ex.dispatch(cont);
552  
                caller_ex.dispatch(cont);
553  
            }
553  
            }
554  
        )(hash_task(password_, rounds_, ver_));
554  
        )(hash_task(password_, rounds_, ver_));
555  
    }
555  
    }
556  

556  

557  
    result await_resume()
557  
    result await_resume()
558  
    {
558  
    {
559  
        if(ep_)
559  
        if(ep_)
560  
            std::rethrow_exception(ep_);
560  
            std::rethrow_exception(ep_);
561  
        return result_;
561  
        return result_;
562  
    }
562  
    }
563  
};
563  
};
564  

564  

565  
struct compare_async_op
565  
struct compare_async_op
566  
{
566  
{
567  
    password_buf password_;
567  
    password_buf password_;
568  
    hash_buf hash_str_;
568  
    hash_buf hash_str_;
569  
    bool result_ = false;
569  
    bool result_ = false;
570  
    std::exception_ptr ep_;
570  
    std::exception_ptr ep_;
571  

571  

572  
    bool await_ready() const noexcept
572  
    bool await_ready() const noexcept
573  
    {
573  
    {
574  
        return false;
574  
        return false;
575  
    }
575  
    }
576  

576  

577  
    void await_suspend(
577  
    void await_suspend(
578  
        capy::coro cont,
578  
        capy::coro cont,
579  
        capy::executor_ref caller_ex,
579  
        capy::executor_ref caller_ex,
580  
        std::stop_token)
580  
        std::stop_token)
581  
    {
581  
    {
582  
        auto& pool = capy::get_system_context();
582  
        auto& pool = capy::get_system_context();
583  
        auto sys_ex = pool.get_executor();
583  
        auto sys_ex = pool.get_executor();
584  
        capy::run_async(sys_ex,
584  
        capy::run_async(sys_ex,
585  
            [this, cont, caller_ex]
585  
            [this, cont, caller_ex]
586  
            (bool ok) mutable
586  
            (bool ok) mutable
587  
            {
587  
            {
588  
                result_ = ok;
588  
                result_ = ok;
589  
                caller_ex.dispatch(cont);
589  
                caller_ex.dispatch(cont);
590  
            },
590  
            },
591  
            [this, cont, caller_ex]
591  
            [this, cont, caller_ex]
592  
            (std::exception_ptr ep) mutable
592  
            (std::exception_ptr ep) mutable
593  
            {
593  
            {
594  
                ep_ = ep;
594  
                ep_ = ep;
595  
                caller_ex.dispatch(cont);
595  
                caller_ex.dispatch(cont);
596  
            }
596  
            }
597  
        )(compare_task(password_, hash_str_));
597  
        )(compare_task(password_, hash_str_));
598  
    }
598  
    }
599  

599  

600  
    bool await_resume()
600  
    bool await_resume()
601  
    {
601  
    {
602  
        if(ep_)
602  
        if(ep_)
603  
            std::rethrow_exception(ep_);
603  
            std::rethrow_exception(ep_);
604  
        return result_;
604  
        return result_;
605  
    }
605  
    }
606  
};
606  
};
607  

607  

608  
} // detail
608  
} // detail
609  

609  

610  
/** Hash a password asynchronously on the system thread pool.
610  
/** Hash a password asynchronously on the system thread pool.
611  

611  

612  
    Returns an awaitable that offloads the CPU-intensive
612  
    Returns an awaitable that offloads the CPU-intensive
613  
    bcrypt work to the system thread pool, then resumes
613  
    bcrypt work to the system thread pool, then resumes
614  
    the caller on their original executor. Modeled after
614  
    the caller on their original executor. Modeled after
615  
    Express.js: `await bcrypt.hash(password, 12)`.
615  
    Express.js: `await bcrypt.hash(password, 12)`.
616  

616  

617  
    @par Example
617  
    @par Example
618  
    @code
618  
    @code
619  
    bcrypt::result r = co_await bcrypt::hash_async("my_password", 12);
619  
    bcrypt::result r = co_await bcrypt::hash_async("my_password", 12);
620  
    @endcode
620  
    @endcode
621  

621  

622  
    @param password The password to hash.
622  
    @param password The password to hash.
623  

623  

624  
    @param rounds Cost factor. Each increment doubles the work.
624  
    @param rounds Cost factor. Each increment doubles the work.
625  

625  

626  
    @param ver Hash version to use.
626  
    @param ver Hash version to use.
627  

627  

628  
    @return An awaitable yielding `result`.
628  
    @return An awaitable yielding `result`.
629  

629  

630  
    @throws std::invalid_argument if rounds is out of range.
630  
    @throws std::invalid_argument if rounds is out of range.
631  
    @throws system_error on RNG failure.
631  
    @throws system_error on RNG failure.
632  
*/
632  
*/
633  
inline
633  
inline
634  
detail::hash_async_op
634  
detail::hash_async_op
635  
hash_async(
635  
hash_async(
636  
    core::string_view password,
636  
    core::string_view password,
637  
    unsigned rounds = 10,
637  
    unsigned rounds = 10,
638  
    version ver = version::v2b)
638  
    version ver = version::v2b)
639  
{
639  
{
640  
    return detail::hash_async_op{
640  
    return detail::hash_async_op{
641  
        detail::password_buf(password),
641  
        detail::password_buf(password),
642  
        rounds,
642  
        rounds,
643  
        ver,
643  
        ver,
644  
        {},
644  
        {},
645  
        {}};
645  
        {}};
646  
}
646  
}
647  

647  

648  
/** Compare a password against a hash asynchronously.
648  
/** Compare a password against a hash asynchronously.
649  

649  

650  
    Returns an awaitable that offloads the CPU-intensive
650  
    Returns an awaitable that offloads the CPU-intensive
651  
    bcrypt work to the system thread pool, then resumes
651  
    bcrypt work to the system thread pool, then resumes
652  
    the caller on their original executor. Modeled after
652  
    the caller on their original executor. Modeled after
653  
    Express.js: `await bcrypt.compare(password, hash)`.
653  
    Express.js: `await bcrypt.compare(password, hash)`.
654  

654  

655  
    @par Example
655  
    @par Example
656  
    @code
656  
    @code
657  
    bool ok = co_await bcrypt::compare_async("my_password", stored_hash);
657  
    bool ok = co_await bcrypt::compare_async("my_password", stored_hash);
658  
    @endcode
658  
    @endcode
659  

659  

660  
    @param password The plaintext password to check.
660  
    @param password The plaintext password to check.
661  

661  

662  
    @param hash_str The hash string to compare against.
662  
    @param hash_str The hash string to compare against.
663  

663  

664  
    @return An awaitable yielding `bool`.
664  
    @return An awaitable yielding `bool`.
665  

665  

666  
    @throws system_error if the hash is malformed.
666  
    @throws system_error if the hash is malformed.
667  
*/
667  
*/
668  
inline
668  
inline
669  
detail::compare_async_op
669  
detail::compare_async_op
670  
compare_async(
670  
compare_async(
671  
    core::string_view password,
671  
    core::string_view password,
672  
    core::string_view hash_str)
672  
    core::string_view hash_str)
673  
{
673  
{
674  
    return detail::compare_async_op{
674  
    return detail::compare_async_op{
675  
        detail::password_buf(password),
675  
        detail::password_buf(password),
676  
        detail::hash_buf(hash_str),
676  
        detail::hash_buf(hash_str),
677  
        false,
677  
        false,
678  
        {}};
678  
        {}};
679  
}
679  
}
680  

680  

681  
} // bcrypt
681  
} // bcrypt
682  
} // http
682  
} // http
683  
} // boost
683  
} // boost
684  

684  

685  
#endif
685  
#endif