94.44% Lines (85/90) 96.30% Functions (26/27)
TLA Baseline Branch
Line Hits Code Line Hits Code
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/continuation.hpp> 44   #include <boost/capy/continuation.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/io_env.hpp> 47   #include <boost/capy/ex/io_env.hpp>
48   #include <boost/capy/ex/run_async.hpp> 48   #include <boost/capy/ex/run_async.hpp>
49   #include <boost/capy/ex/system_context.hpp> 49   #include <boost/capy/ex/system_context.hpp>
50   50  
51   #include <cstddef> 51   #include <cstddef>
52   #include <cstring> 52   #include <cstring>
53   #include <exception> 53   #include <exception>
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
HITCBC 146   17 make_error_code( 146   17 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),
HITCBC 152   17 detail::error_cat}; 152   17 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   */
HITCBC 179   31 result() noexcept 179   31 result() noexcept
HITCBC 180   31 : size_(0) 180   31 : size_(0)
181   { 181   {
HITCBC 182   31 buf_[0] = '\0'; 182   31 buf_[0] = '\0';
HITCBC 183   31 } 183   31 }
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
HITCBC 188   30 str() const noexcept 188   30 str() const noexcept
189   { 189   {
HITCBC 190   30 return core::string_view(buf_, size_); 190   30 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*
HITCBC 203   1 c_str() const noexcept 203   1 c_str() const noexcept
204   { 204   {
HITCBC 205   1 return buf_; 205   1 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
HITCBC 219   7 size() const noexcept 219   7 size() const noexcept
220   { 220   {
HITCBC 221   7 return size_; 221   7 return size_;
222   } 222   }
223   223  
224   /** Check if result is empty. 224   /** Check if result is empty.
225   */ 225   */
226   bool 226   bool
HITCBC 227   4 empty() const noexcept 227   4 empty() const noexcept
228   { 228   {
HITCBC 229   4 return size_ == 0; 229   4 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
HITCBC 235   2 operator bool() const noexcept 235   2 operator bool() const noexcept
236   { 236   {
HITCBC 237   2 return size_ != 0; 237   2 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  
HITCBC 245   25 char* buf() noexcept { return buf_; } 245   25 char* buf() noexcept { return buf_; }
HITCBC 246   25 void set_size(unsigned char n) noexcept 246   25 void set_size(unsigned char n) noexcept
247   { 247   {
HITCBC 248   25 size_ = n; 248   25 size_ = n;
HITCBC 249   25 buf_[n] = '\0'; 249   25 buf_[n] = '\0';
HITCBC 250   25 } 250   25 }
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  
HITCBC 408   14 explicit password_buf( 408   14 explicit password_buf(
409   core::string_view s) noexcept 409   core::string_view s) noexcept
HITCBC 410   28 : size_(static_cast<unsigned char>( 410   28 : size_(static_cast<unsigned char>(
HITCBC 411   14 (std::min)(s.size(), std::size_t{72}))) 411   14 (std::min)(s.size(), std::size_t{72})))
412   { 412   {
HITCBC 413   14 std::memcpy(data_, s.data(), size_); 413   14 std::memcpy(data_, s.data(), size_);
HITCBC 414   14 } 414   14 }
415   415  
HITCBC 416   14 operator core::string_view() const noexcept 416   14 operator core::string_view() const noexcept
417   { 417   {
HITCBC 418   14 return {data_, size_}; 418   14 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  
HITCBC 428   9 explicit hash_buf( 428   9 explicit hash_buf(
429   core::string_view s) noexcept 429   core::string_view s) noexcept
HITCBC 430   18 : size_(static_cast<unsigned char>( 430   18 : size_(static_cast<unsigned char>(
HITCBC 431   9 (std::min)(s.size(), std::size_t{60}))) 431   9 (std::min)(s.size(), std::size_t{60})))
432   { 432   {
HITCBC 433   9 std::memcpy(data_, s.data(), size_); 433   9 std::memcpy(data_, s.data(), size_);
HITCBC 434   9 data_[size_] = '\0'; 434   9 data_[size_] = '\0';
HITCBC 435   9 } 435   9 }
436   436  
HITCBC 437   9 operator core::string_view() const noexcept 437   9 operator core::string_view() const noexcept
438   { 438   {
HITCBC 439   9 return {data_, size_}; 439   9 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>
HITCBC 475   4 hash_task( 475   4 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);
HITCBC 482   8 } 482   8 }
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>
HITCBC 504   6 compare_task( 504   6 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;
HITCBC 515   12 } 515   12 }
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   capy::continuation cont_; 528   capy::continuation cont_;
529   529  
HITCBC 530   1 bool await_ready() const noexcept 530   1 bool await_ready() const noexcept
531   { 531   {
HITCBC 532   1 return false; 532   1 return false;
533   } 533   }
534   534  
HITCBC 535   1 void await_suspend( 535   1 void await_suspend(
536   std::coroutine_handle<void> cont, 536   std::coroutine_handle<void> cont,
537   capy::io_env const* env) 537   capy::io_env const* env)
538   { 538   {
HITCBC 539   1 cont_.h = cont; 539   1 cont_.h = cont;
HITCBC 540   1 auto caller_ex = env->executor; 540   1 auto caller_ex = env->executor;
HITCBC 541   1 auto& pool = capy::get_system_context(); 541   1 auto& pool = capy::get_system_context();
HITCBC 542   1 auto sys_ex = pool.get_executor(); 542   1 auto sys_ex = pool.get_executor();
HITCBC 543   1 capy::run_async(sys_ex, 543   1 capy::run_async(sys_ex,
HITCBC 544   1 [this, caller_ex] 544   1 [this, caller_ex]
545   (result r) mutable 545   (result r) mutable
546   { 546   {
HITCBC 547   1 result_ = r; 547   1 result_ = r;
HITCBC 548   1 caller_ex.dispatch(cont_).resume(); 548   1 caller_ex.dispatch(cont_).resume();
HITCBC 549   1 }, 549   1 },
MISUBC 550   [this, caller_ex] 550   [this, caller_ex]
551   (std::exception_ptr ep) mutable 551   (std::exception_ptr ep) mutable
552   { 552   {
MISUBC 553   ep_ = ep; 553   ep_ = ep;
MISUBC 554   caller_ex.dispatch(cont_).resume(); 554   caller_ex.dispatch(cont_).resume();
MISUBC 555   } 555   }
HITCBC 556   1 )(hash_task(password_, rounds_, ver_)); 556   1 )(hash_task(password_, rounds_, ver_));
HITCBC 557   1 } 557   1 }
558   558  
HITCBC 559   1 result await_resume() 559   1 result await_resume()
560   { 560   {
HITCBC 561   1 if(ep_) 561   1 if(ep_)
MISUBC 562   std::rethrow_exception(ep_); 562   std::rethrow_exception(ep_);
HITCBC 563   1 return result_; 563   1 return result_;
564   } 564   }
565   }; 565   };
566   566  
567   struct compare_async_op 567   struct compare_async_op
568   { 568   {
569   password_buf password_; 569   password_buf password_;
570   hash_buf hash_str_; 570   hash_buf hash_str_;
571   bool result_ = false; 571   bool result_ = false;
572   std::exception_ptr ep_; 572   std::exception_ptr ep_;
573   capy::continuation cont_; 573   capy::continuation cont_;
574   574  
HITCBC 575   3 bool await_ready() const noexcept 575   3 bool await_ready() const noexcept
576   { 576   {
HITCBC 577   3 return false; 577   3 return false;
578   } 578   }
579   579  
HITCBC 580   3 void await_suspend( 580   3 void await_suspend(
581   std::coroutine_handle<void> cont, 581   std::coroutine_handle<void> cont,
582   capy::io_env const* env) 582   capy::io_env const* env)
583   { 583   {
HITCBC 584   3 cont_.h = cont; 584   3 cont_.h = cont;
HITCBC 585   3 auto caller_ex = env->executor; 585   3 auto caller_ex = env->executor;
HITCBC 586   3 auto& pool = capy::get_system_context(); 586   3 auto& pool = capy::get_system_context();
HITCBC 587   3 auto sys_ex = pool.get_executor(); 587   3 auto sys_ex = pool.get_executor();
HITCBC 588   3 capy::run_async(sys_ex, 588   3 capy::run_async(sys_ex,
HITCBC 589   2 [this, caller_ex] 589   2 [this, caller_ex]
590   (bool ok) mutable 590   (bool ok) mutable
591   { 591   {
HITCBC 592   2 result_ = ok; 592   2 result_ = ok;
HITCBC 593   2 caller_ex.dispatch(cont_).resume(); 593   2 caller_ex.dispatch(cont_).resume();
HITCBC 594   2 }, 594   2 },
HITCBC 595   1 [this, caller_ex] 595   1 [this, caller_ex]
596   (std::exception_ptr ep) mutable 596   (std::exception_ptr ep) mutable
597   { 597   {
HITCBC 598   1 ep_ = ep; 598   1 ep_ = ep;
HITCBC 599   1 caller_ex.dispatch(cont_).resume(); 599   1 caller_ex.dispatch(cont_).resume();
HITCBC 600   1 } 600   1 }
HITCBC 601   3 )(compare_task(password_, hash_str_)); 601   3 )(compare_task(password_, hash_str_));
HITCBC 602   3 } 602   3 }
603   603  
HITCBC 604   3 bool await_resume() 604   3 bool await_resume()
605   { 605   {
HITCBC 606   3 if(ep_) 606   3 if(ep_)
HITCBC 607   1 std::rethrow_exception(ep_); 607   1 std::rethrow_exception(ep_);
HITCBC 608   2 return result_; 608   2 return result_;
609   } 609   }
610   }; 610   };
611   611  
612   } // detail 612   } // detail
613   613  
614   /** Hash a password asynchronously on the system thread pool. 614   /** Hash a password asynchronously on the system thread pool.
615   615  
616   Returns an awaitable that offloads the CPU-intensive 616   Returns an awaitable that offloads the CPU-intensive
617   bcrypt work to the system thread pool, then resumes 617   bcrypt work to the system thread pool, then resumes
618   the caller on their original executor. Modeled after 618   the caller on their original executor. Modeled after
619   Express.js: `await bcrypt.hash(password, 12)`. 619   Express.js: `await bcrypt.hash(password, 12)`.
620   620  
621   @par Example 621   @par Example
622   @code 622   @code
623   bcrypt::result r = co_await bcrypt::hash_async("my_password", 12); 623   bcrypt::result r = co_await bcrypt::hash_async("my_password", 12);
624   @endcode 624   @endcode
625   625  
626   @param password The password to hash. 626   @param password The password to hash.
627   627  
628   @param rounds Cost factor. Each increment doubles the work. 628   @param rounds Cost factor. Each increment doubles the work.
629   629  
630   @param ver Hash version to use. 630   @param ver Hash version to use.
631   631  
632   @return An awaitable yielding `result`. 632   @return An awaitable yielding `result`.
633   633  
634   @throws std::invalid_argument if rounds is out of range. 634   @throws std::invalid_argument if rounds is out of range.
635   @throws system_error on RNG failure. 635   @throws system_error on RNG failure.
636   */ 636   */
637   inline 637   inline
638   detail::hash_async_op 638   detail::hash_async_op
HITCBC 639   1 hash_async( 639   1 hash_async(
640   core::string_view password, 640   core::string_view password,
641   unsigned rounds = 10, 641   unsigned rounds = 10,
642   version ver = version::v2b) 642   version ver = version::v2b)
643   { 643   {
HITCBC 644   1 return detail::hash_async_op{ 644   1 return detail::hash_async_op{
645   detail::password_buf(password), 645   detail::password_buf(password),
646   rounds, 646   rounds,
647   ver, 647   ver,
648   {}, 648   {},
649   {}, 649   {},
HITCBC 650   1 {}}; 650   1 {}};
651   } 651   }
652   652  
653   /** Compare a password against a hash asynchronously. 653   /** Compare a password against a hash asynchronously.
654   654  
655   Returns an awaitable that offloads the CPU-intensive 655   Returns an awaitable that offloads the CPU-intensive
656   bcrypt work to the system thread pool, then resumes 656   bcrypt work to the system thread pool, then resumes
657   the caller on their original executor. Modeled after 657   the caller on their original executor. Modeled after
658   Express.js: `await bcrypt.compare(password, hash)`. 658   Express.js: `await bcrypt.compare(password, hash)`.
659   659  
660   @par Example 660   @par Example
661   @code 661   @code
662   bool ok = co_await bcrypt::compare_async("my_password", stored_hash); 662   bool ok = co_await bcrypt::compare_async("my_password", stored_hash);
663   @endcode 663   @endcode
664   664  
665   @param password The plaintext password to check. 665   @param password The plaintext password to check.
666   666  
667   @param hash_str The hash string to compare against. 667   @param hash_str The hash string to compare against.
668   668  
669   @return An awaitable yielding `bool`. 669   @return An awaitable yielding `bool`.
670   670  
671   @throws system_error if the hash is malformed. 671   @throws system_error if the hash is malformed.
672   */ 672   */
673   inline 673   inline
674   detail::compare_async_op 674   detail::compare_async_op
HITCBC 675   3 compare_async( 675   3 compare_async(
676   core::string_view password, 676   core::string_view password,
677   core::string_view hash_str) 677   core::string_view hash_str)
678   { 678   {
HITCBC 679   3 return detail::compare_async_op{ 679   3 return detail::compare_async_op{
680   detail::password_buf(password), 680   detail::password_buf(password),
681   detail::hash_buf(hash_str), 681   detail::hash_buf(hash_str),
682   false, 682   false,
683   {}, 683   {},
HITCBC 684   3 {}}; 684   3 {}};
685   } 685   }
686   686  
687   } // bcrypt 687   } // bcrypt
688   } // http 688   } // http
689   } // boost 689   } // boost
690   690  
691   #endif 691   #endif