94.51% Lines (86/91) 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   std::error_code ec; 18   std::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   40  
41   #include <boost/capy/continuation.hpp> 41   #include <boost/capy/continuation.hpp>
42   #include <boost/capy/task.hpp> 42   #include <boost/capy/task.hpp>
43   #include <boost/capy/ex/executor_ref.hpp> 43   #include <boost/capy/ex/executor_ref.hpp>
44   #include <boost/capy/ex/io_env.hpp> 44   #include <boost/capy/ex/io_env.hpp>
45   #include <boost/capy/ex/run_async.hpp> 45   #include <boost/capy/ex/run_async.hpp>
46   #include <boost/capy/ex/system_context.hpp> 46   #include <boost/capy/ex/system_context.hpp>
47   47  
48   #include <cstddef> 48   #include <cstddef>
49   #include <cstring> 49   #include <cstring>
50   #include <exception> 50   #include <exception>
51   #include <string> 51   #include <string>
52   #include <system_error> 52   #include <system_error>
53   53  
54   namespace boost { 54   namespace boost {
55   namespace http { 55   namespace http {
56   namespace bcrypt { 56   namespace bcrypt {
57   57  
58   //------------------------------------------------ 58   //------------------------------------------------
59   59  
60   /** bcrypt hash version prefix. 60   /** bcrypt hash version prefix.
61   61  
62   The version determines which variant of bcrypt is used. 62   The version determines which variant of bcrypt is used.
63   All versions produce compatible hashes. 63   All versions produce compatible hashes.
64   */ 64   */
65   enum class version 65   enum class version
66   { 66   {
67   /// $2a$ - Original specification 67   /// $2a$ - Original specification
68   v2a, 68   v2a,
69   69  
70   /// $2b$ - Fixed handling of passwords > 255 chars (recommended) 70   /// $2b$ - Fixed handling of passwords > 255 chars (recommended)
71   v2b 71   v2b
72   }; 72   };
73   73  
74   //------------------------------------------------ 74   //------------------------------------------------
75   75  
76   /** Error codes for bcrypt operations. 76   /** Error codes for bcrypt operations.
77   77  
78   These errors indicate malformed input from untrusted sources. 78   These errors indicate malformed input from untrusted sources.
79   */ 79   */
80   enum class error 80   enum class error
81   { 81   {
82   /// Success 82   /// Success
83   ok = 0, 83   ok = 0,
84   84  
85   /// Salt string is malformed 85   /// Salt string is malformed
86   invalid_salt, 86   invalid_salt,
87   87  
88   /// Hash string is malformed 88   /// Hash string is malformed
89   invalid_hash 89   invalid_hash
90   }; 90   };
91   91  
92   } // bcrypt 92   } // bcrypt
93   } // http 93   } // http
94   94  
95   } // boost 95   } // boost
96   96  
97   namespace std { 97   namespace std {
98   template<> 98   template<>
99   struct is_error_code_enum< 99   struct is_error_code_enum<
100   ::boost::http::bcrypt::error> 100   ::boost::http::bcrypt::error>
101   : std::true_type {}; 101   : std::true_type {};
102   } // std 102   } // std
103   103  
104   namespace boost { 104   namespace boost {
105   namespace http { 105   namespace http {
106   namespace bcrypt { 106   namespace bcrypt {
107   107  
108   namespace detail { 108   namespace detail {
109   109  
110   struct BOOST_SYMBOL_VISIBLE 110   struct BOOST_SYMBOL_VISIBLE
111   error_cat_type 111   error_cat_type
112   : std::error_category 112   : std::error_category
113   { 113   {
114   BOOST_HTTP_DECL const char* name( 114   BOOST_HTTP_DECL const char* name(
115   ) const noexcept override; 115   ) const noexcept override;
116   BOOST_HTTP_DECL std::string message( 116   BOOST_HTTP_DECL std::string message(
117   int) const override; 117   int) const override;
118   constexpr error_cat_type() noexcept = default; 118   constexpr error_cat_type() noexcept = default;
119   }; 119   };
120   120  
121   BOOST_HTTP_DECL extern 121   BOOST_HTTP_DECL extern
122   error_cat_type error_cat; 122   error_cat_type error_cat;
123   123  
124   } // detail 124   } // detail
125   125  
126   inline 126   inline
127   std::error_code 127   std::error_code
HITCBC 128   17 make_error_code( 128   17 make_error_code(
129   error ev) noexcept 129   error ev) noexcept
130   { 130   {
HITCBC 131   17 return std::error_code{ 131   17 return std::error_code{
132   static_cast<std::underlying_type< 132   static_cast<std::underlying_type<
133   error>::type>(ev), 133   error>::type>(ev),
HITCBC 134   17 detail::error_cat}; 134   17 detail::error_cat};
135   } 135   }
136   136  
137   //------------------------------------------------ 137   //------------------------------------------------
138   138  
139   /** Fixed-size buffer for bcrypt hash output. 139   /** Fixed-size buffer for bcrypt hash output.
140   140  
141   Stores a bcrypt hash string (max 60 chars) in an 141   Stores a bcrypt hash string (max 60 chars) in an
142   inline buffer with no heap allocation. 142   inline buffer with no heap allocation.
143   143  
144   @par Example 144   @par Example
145   @code 145   @code
146   bcrypt::result r = bcrypt::hash("password", 10); 146   bcrypt::result r = bcrypt::hash("password", 10);
147   core::string_view sv = r; // or r.str() 147   core::string_view sv = r; // or r.str()
148   std::cout << r.c_str(); // null-terminated 148   std::cout << r.c_str(); // null-terminated
149   @endcode 149   @endcode
150   */ 150   */
151   class result 151   class result
152   { 152   {
153   char buf_[61]; 153   char buf_[61];
154   unsigned char size_; 154   unsigned char size_;
155   155  
156   public: 156   public:
157   /** Default constructor. 157   /** Default constructor.
158   158  
159   Constructs an empty result. 159   Constructs an empty result.
160   */ 160   */
HITCBC 161   31 result() noexcept 161   31 result() noexcept
HITCBC 162   31 : size_(0) 162   31 : size_(0)
163   { 163   {
HITCBC 164   31 buf_[0] = '\0'; 164   31 buf_[0] = '\0';
HITCBC 165   31 } 165   31 }
166   166  
167   /** Return the hash as a string_view. 167   /** Return the hash as a string_view.
168   */ 168   */
169   core::string_view 169   core::string_view
HITCBC 170   30 str() const noexcept 170   30 str() const noexcept
171   { 171   {
HITCBC 172   30 return core::string_view(buf_, size_); 172   30 return core::string_view(buf_, size_);
173   } 173   }
174   174  
175   /** Implicit conversion to string_view. 175   /** Implicit conversion to string_view.
176   */ 176   */
177   operator core::string_view() const noexcept 177   operator core::string_view() const noexcept
178   { 178   {
179   return str(); 179   return str();
180   } 180   }
181   181  
182   /** Return null-terminated C string. 182   /** Return null-terminated C string.
183   */ 183   */
184   char const* 184   char const*
HITCBC 185   1 c_str() const noexcept 185   1 c_str() const noexcept
186   { 186   {
HITCBC 187   1 return buf_; 187   1 return buf_;
188   } 188   }
189   189  
190   /** Return pointer to data. 190   /** Return pointer to data.
191   */ 191   */
192   char const* 192   char const*
193   data() const noexcept 193   data() const noexcept
194   { 194   {
195   return buf_; 195   return buf_;
196   } 196   }
197   197  
198   /** Return size in bytes (excludes null terminator). 198   /** Return size in bytes (excludes null terminator).
199   */ 199   */
200   std::size_t 200   std::size_t
HITCBC 201   7 size() const noexcept 201   7 size() const noexcept
202   { 202   {
HITCBC 203   7 return size_; 203   7 return size_;
204   } 204   }
205   205  
206   /** Check if result is empty. 206   /** Check if result is empty.
207   */ 207   */
208   bool 208   bool
HITCBC 209   4 empty() const noexcept 209   4 empty() const noexcept
210   { 210   {
HITCBC 211   4 return size_ == 0; 211   4 return size_ == 0;
212   } 212   }
213   213  
214   /** Check if result contains valid data. 214   /** Check if result contains valid data.
215   */ 215   */
216   explicit 216   explicit
HITCBC 217   2 operator bool() const noexcept 217   2 operator bool() const noexcept
218   { 218   {
HITCBC 219   2 return size_ != 0; 219   2 return size_ != 0;
220   } 220   }
221   221  
222   private: 222   private:
223   friend BOOST_HTTP_DECL result gen_salt(unsigned, version); 223   friend BOOST_HTTP_DECL result gen_salt(unsigned, version);
224   friend BOOST_HTTP_DECL result hash(core::string_view, unsigned, version); 224   friend BOOST_HTTP_DECL result hash(core::string_view, unsigned, version);
225   friend BOOST_HTTP_DECL result hash(core::string_view, core::string_view, std::error_code&); 225   friend BOOST_HTTP_DECL result hash(core::string_view, core::string_view, std::error_code&);
226   226  
HITCBC 227   25 char* buf() noexcept { return buf_; } 227   25 char* buf() noexcept { return buf_; }
HITCBC 228   25 void set_size(unsigned char n) noexcept 228   25 void set_size(unsigned char n) noexcept
229   { 229   {
HITCBC 230   25 size_ = n; 230   25 size_ = n;
HITCBC 231   25 buf_[n] = '\0'; 231   25 buf_[n] = '\0';
HITCBC 232   25 } 232   25 }
233   }; 233   };
234   234  
235   //------------------------------------------------ 235   //------------------------------------------------
236   236  
237   /** Generate a random salt. 237   /** Generate a random salt.
238   238  
239   Creates a bcrypt salt string suitable for use with 239   Creates a bcrypt salt string suitable for use with
240   the hash() function. 240   the hash() function.
241   241  
242   @par Preconditions 242   @par Preconditions
243   @code 243   @code
244   rounds >= 4 && rounds <= 31 244   rounds >= 4 && rounds <= 31
245   @endcode 245   @endcode
246   246  
247   @par Exception Safety 247   @par Exception Safety
248   Strong guarantee. 248   Strong guarantee.
249   249  
250   @par Complexity 250   @par Complexity
251   Constant. 251   Constant.
252   252  
253   @param rounds Cost factor. Each increment doubles the work. 253   @param rounds Cost factor. Each increment doubles the work.
254   Default is 10, which takes approximately 100ms on modern hardware. 254   Default is 10, which takes approximately 100ms on modern hardware.
255   255  
256   @param ver Hash version to use. 256   @param ver Hash version to use.
257   257  
258   @return A 29-character salt string. 258   @return A 29-character salt string.
259   259  
260   @throws std::invalid_argument if rounds is out of range. 260   @throws std::invalid_argument if rounds is out of range.
261   @throws system_error on RNG failure. 261   @throws system_error on RNG failure.
262   */ 262   */
263   BOOST_HTTP_DECL 263   BOOST_HTTP_DECL
264   result 264   result
265   gen_salt( 265   gen_salt(
266   unsigned rounds = 10, 266   unsigned rounds = 10,
267   version ver = version::v2b); 267   version ver = version::v2b);
268   268  
269   /** Hash a password with auto-generated salt. 269   /** Hash a password with auto-generated salt.
270   270  
271   Generates a random salt and hashes the password. 271   Generates a random salt and hashes the password.
272   272  
273   @par Preconditions 273   @par Preconditions
274   @code 274   @code
275   rounds >= 4 && rounds <= 31 275   rounds >= 4 && rounds <= 31
276   @endcode 276   @endcode
277   277  
278   @par Exception Safety 278   @par Exception Safety
279   Strong guarantee. 279   Strong guarantee.
280   280  
281   @par Complexity 281   @par Complexity
282   O(2^rounds). 282   O(2^rounds).
283   283  
284   @param password The password to hash. Only the first 72 bytes 284   @param password The password to hash. Only the first 72 bytes
285   are used (bcrypt limitation). 285   are used (bcrypt limitation).
286   286  
287   @param rounds Cost factor. Each increment doubles the work. 287   @param rounds Cost factor. Each increment doubles the work.
288   288  
289   @param ver Hash version to use. 289   @param ver Hash version to use.
290   290  
291   @return A 60-character hash string. 291   @return A 60-character hash string.
292   292  
293   @throws std::invalid_argument if rounds is out of range. 293   @throws std::invalid_argument if rounds is out of range.
294   @throws system_error on RNG failure. 294   @throws system_error on RNG failure.
295   */ 295   */
296   BOOST_HTTP_DECL 296   BOOST_HTTP_DECL
297   result 297   result
298   hash( 298   hash(
299   core::string_view password, 299   core::string_view password,
300   unsigned rounds = 10, 300   unsigned rounds = 10,
301   version ver = version::v2b); 301   version ver = version::v2b);
302   302  
303   /** Hash a password using a provided salt. 303   /** Hash a password using a provided salt.
304   304  
305   Uses the given salt to hash the password. The salt should 305   Uses the given salt to hash the password. The salt should
306   be a string previously returned by gen_salt() or extracted 306   be a string previously returned by gen_salt() or extracted
307   from a hash string. 307   from a hash string.
308   308  
309   @par Exception Safety 309   @par Exception Safety
310   Strong guarantee. 310   Strong guarantee.
311   311  
312   @par Complexity 312   @par Complexity
313   O(2^rounds). 313   O(2^rounds).
314   314  
315   @param password The password to hash. 315   @param password The password to hash.
316   316  
317   @param salt The salt string (29 characters). 317   @param salt The salt string (29 characters).
318   318  
319   @param ec Set to bcrypt::error::invalid_salt if the salt 319   @param ec Set to bcrypt::error::invalid_salt if the salt
320   is malformed. 320   is malformed.
321   321  
322   @return A 60-character hash string, or empty result on error. 322   @return A 60-character hash string, or empty result on error.
323   */ 323   */
324   BOOST_HTTP_DECL 324   BOOST_HTTP_DECL
325   result 325   result
326   hash( 326   hash(
327   core::string_view password, 327   core::string_view password,
328   core::string_view salt, 328   core::string_view salt,
329   std::error_code& ec); 329   std::error_code& ec);
330   330  
331   /** Compare a password against a hash. 331   /** Compare a password against a hash.
332   332  
333   Extracts the salt from the hash, re-hashes the password, 333   Extracts the salt from the hash, re-hashes the password,
334   and compares the result. 334   and compares the result.
335   335  
336   @par Exception Safety 336   @par Exception Safety
337   Strong guarantee. 337   Strong guarantee.
338   338  
339   @par Complexity 339   @par Complexity
340   O(2^rounds). 340   O(2^rounds).
341   341  
342   @param password The plaintext password to check. 342   @param password The plaintext password to check.
343   343  
344   @param hash The hash string to compare against. 344   @param hash The hash string to compare against.
345   345  
346   @param ec Set to bcrypt::error::invalid_hash if the hash 346   @param ec Set to bcrypt::error::invalid_hash if the hash
347   is malformed. 347   is malformed.
348   348  
349   @return true if the password matches the hash, false if 349   @return true if the password matches the hash, false if
350   it does not match OR if an error occurred. Always check 350   it does not match OR if an error occurred. Always check
351   ec to distinguish between a mismatch and an error. 351   ec to distinguish between a mismatch and an error.
352   */ 352   */
353   BOOST_HTTP_DECL 353   BOOST_HTTP_DECL
354   bool 354   bool
355   compare( 355   compare(
356   core::string_view password, 356   core::string_view password,
357   core::string_view hash, 357   core::string_view hash,
358   std::error_code& ec); 358   std::error_code& ec);
359   359  
360   /** Extract the cost factor from a hash string. 360   /** Extract the cost factor from a hash string.
361   361  
362   @par Exception Safety 362   @par Exception Safety
363   Strong guarantee. 363   Strong guarantee.
364   364  
365   @par Complexity 365   @par Complexity
366   Constant. 366   Constant.
367   367  
368   @param hash The hash string to parse. 368   @param hash The hash string to parse.
369   369  
370   @param ec Set to bcrypt::error::invalid_hash if the hash 370   @param ec Set to bcrypt::error::invalid_hash if the hash
371   is malformed. 371   is malformed.
372   372  
373   @return The cost factor (4-31) on success, or 0 if an 373   @return The cost factor (4-31) on success, or 0 if an
374   error occurred. 374   error occurred.
375   */ 375   */
376   BOOST_HTTP_DECL 376   BOOST_HTTP_DECL
377   unsigned 377   unsigned
378   get_rounds( 378   get_rounds(
379   core::string_view hash, 379   core::string_view hash,
380   std::error_code& ec); 380   std::error_code& ec);
381   381  
382   namespace detail { 382   namespace detail {
383   383  
384   // bcrypt truncates passwords to 72 bytes 384   // bcrypt truncates passwords to 72 bytes
385   struct password_buf 385   struct password_buf
386   { 386   {
387   char data_[72]; 387   char data_[72];
388   unsigned char size_; 388   unsigned char size_;
389   389  
HITCBC 390   14 explicit password_buf( 390   14 explicit password_buf(
391   core::string_view s) noexcept 391   core::string_view s) noexcept
HITCBC 392   28 : size_(static_cast<unsigned char>( 392   28 : size_(static_cast<unsigned char>(
HITCBC 393   14 (std::min)(s.size(), std::size_t{72}))) 393   14 (std::min)(s.size(), std::size_t{72})))
394   { 394   {
HITCBC 395   14 std::memcpy(data_, s.data(), size_); 395   14 std::memcpy(data_, s.data(), size_);
HITCBC 396   14 } 396   14 }
397   397  
HITCBC 398   14 operator core::string_view() const noexcept 398   14 operator core::string_view() const noexcept
399   { 399   {
HITCBC 400   14 return {data_, size_}; 400   14 return {data_, size_};
401   } 401   }
402   }; 402   };
403   403  
404   // bcrypt hashes are always 60 characters 404   // bcrypt hashes are always 60 characters
405   struct hash_buf 405   struct hash_buf
406   { 406   {
407   char data_[61]; 407   char data_[61];
408   unsigned char size_; 408   unsigned char size_;
409   409  
HITCBC 410   9 explicit hash_buf( 410   9 explicit hash_buf(
411   core::string_view s) noexcept 411   core::string_view s) noexcept
HITCBC 412   18 : size_(static_cast<unsigned char>( 412   18 : size_(static_cast<unsigned char>(
HITCBC 413   9 (std::min)(s.size(), std::size_t{60}))) 413   9 (std::min)(s.size(), std::size_t{60})))
414   { 414   {
HITCBC 415   9 std::memcpy(data_, s.data(), size_); 415   9 std::memcpy(data_, s.data(), size_);
HITCBC 416   9 data_[size_] = '\0'; 416   9 data_[size_] = '\0';
HITCBC 417   9 } 417   9 }
418   418  
HITCBC 419   9 operator core::string_view() const noexcept 419   9 operator core::string_view() const noexcept
420   { 420   {
HITCBC 421   9 return {data_, size_}; 421   9 return {data_, size_};
422   } 422   }
423   }; 423   };
424   424  
425   } // detail 425   } // detail
426   426  
427   //------------------------------------------------ 427   //------------------------------------------------
428   428  
429   /** Hash a password, returning a lazy task. 429   /** Hash a password, returning a lazy task.
430   430  
431   Returns a @ref capy::task that wraps the synchronous 431   Returns a @ref capy::task that wraps the synchronous
432   hash() call. The caller can co_await this task directly 432   hash() call. The caller can co_await this task directly
433   or launch it on a specific executor via run_async(). 433   or launch it on a specific executor via run_async().
434   434  
435   @par Example 435   @par Example
436   @code 436   @code
437   // co_await in current context 437   // co_await in current context
438   bcrypt::result r = co_await bcrypt::hash_task("password", 12); 438   bcrypt::result r = co_await bcrypt::hash_task("password", 12);
439   439  
440   // or launch on a specific executor 440   // or launch on a specific executor
441   run_async(my_executor)(bcrypt::hash_task("password", 12)); 441   run_async(my_executor)(bcrypt::hash_task("password", 12));
442   @endcode 442   @endcode
443   443  
444   @param password The password to hash. 444   @param password The password to hash.
445   445  
446   @param rounds Cost factor. Each increment doubles the work. 446   @param rounds Cost factor. Each increment doubles the work.
447   447  
448   @param ver Hash version to use. 448   @param ver Hash version to use.
449   449  
450   @return A lazy task yielding `result`. 450   @return A lazy task yielding `result`.
451   451  
452   @throws std::invalid_argument if rounds is out of range. 452   @throws std::invalid_argument if rounds is out of range.
453   @throws system_error on RNG failure. 453   @throws system_error on RNG failure.
454   */ 454   */
455   inline 455   inline
456   capy::task<result> 456   capy::task<result>
HITCBC 457   4 hash_task( 457   4 hash_task(
458   core::string_view password, 458   core::string_view password,
459   unsigned rounds = 10, 459   unsigned rounds = 10,
460   version ver = version::v2b) 460   version ver = version::v2b)
461   { 461   {
462   detail::password_buf pw(password); 462   detail::password_buf pw(password);
463   co_return hash(pw, rounds, ver); 463   co_return hash(pw, rounds, ver);
HITCBC 464   8 } 464   8 }
465   465  
466   /** Compare a password against a hash, returning a lazy task. 466   /** Compare a password against a hash, returning a lazy task.
467   467  
468   Returns a @ref capy::task that wraps the synchronous 468   Returns a @ref capy::task that wraps the synchronous
469   compare() call. Errors are translated to exceptions. 469   compare() call. Errors are translated to exceptions.
470   470  
471   @par Example 471   @par Example
472   @code 472   @code
473   bool ok = co_await bcrypt::compare_task("password", stored_hash); 473   bool ok = co_await bcrypt::compare_task("password", stored_hash);
474   @endcode 474   @endcode
475   475  
476   @param password The plaintext password to check. 476   @param password The plaintext password to check.
477   477  
478   @param hash_str The hash string to compare against. 478   @param hash_str The hash string to compare against.
479   479  
480   @return A lazy task yielding `bool`. 480   @return A lazy task yielding `bool`.
481   481  
482   @throws system_error if the hash is malformed. 482   @throws system_error if the hash is malformed.
483   */ 483   */
484   inline 484   inline
485   capy::task<bool> 485   capy::task<bool>
HITCBC 486   6 compare_task( 486   6 compare_task(
487   core::string_view password, 487   core::string_view password,
488   core::string_view hash_str) 488   core::string_view hash_str)
489   { 489   {
490   detail::password_buf pw(password); 490   detail::password_buf pw(password);
491   detail::hash_buf hs(hash_str); 491   detail::hash_buf hs(hash_str);
492   std::error_code ec; 492   std::error_code ec;
493   bool ok = compare(pw, hs, ec); 493   bool ok = compare(pw, hs, ec);
494   if(ec) 494   if(ec)
495   http::detail::throw_system_error(ec); 495   http::detail::throw_system_error(ec);
496   co_return ok; 496   co_return ok;
HITCBC 497   12 } 497   12 }
498   498  
499   //------------------------------------------------ 499   //------------------------------------------------
500   500  
501   namespace detail { 501   namespace detail {
502   502  
503   struct hash_async_op 503   struct hash_async_op
504   { 504   {
505   password_buf password_; 505   password_buf password_;
506   unsigned rounds_; 506   unsigned rounds_;
507   version ver_; 507   version ver_;
508   result result_; 508   result result_;
509   std::exception_ptr ep_; 509   std::exception_ptr ep_;
510   capy::continuation cont_; 510   capy::continuation cont_;
511   511  
HITCBC 512   1 bool await_ready() const noexcept 512   1 bool await_ready() const noexcept
513   { 513   {
HITCBC 514   1 return false; 514   1 return false;
515   } 515   }
516   516  
HITCBC 517   1 void await_suspend( 517   1 void await_suspend(
518   std::coroutine_handle<void> cont, 518   std::coroutine_handle<void> cont,
519   capy::io_env const* env) 519   capy::io_env const* env)
520   { 520   {
HITCBC 521   1 cont_.h = cont; 521   1 cont_.h = cont;
HITCBC 522   1 auto caller_ex = env->executor; 522   1 auto caller_ex = env->executor;
HITCBC 523   1 auto& pool = capy::get_system_context(); 523   1 auto& pool = capy::get_system_context();
HITCBC 524   1 auto sys_ex = pool.get_executor(); 524   1 auto sys_ex = pool.get_executor();
HITCBC 525   1 capy::run_async(sys_ex, 525   1 capy::run_async(sys_ex,
HITCBC 526   1 [this, caller_ex] 526   1 [this, caller_ex]
527   (result r) mutable 527   (result r) mutable
528   { 528   {
HITCBC 529   1 result_ = r; 529   1 result_ = r;
HITCBC 530   1 caller_ex.dispatch(cont_).resume(); 530   1 caller_ex.dispatch(cont_).resume();
HITCBC 531   1 }, 531   1 },
MISUBC 532   [this, caller_ex] 532   [this, caller_ex]
533   (std::exception_ptr ep) mutable 533   (std::exception_ptr ep) mutable
534   { 534   {
MISUBC 535   ep_ = ep; 535   ep_ = ep;
MISUBC 536   caller_ex.dispatch(cont_).resume(); 536   caller_ex.dispatch(cont_).resume();
MISUBC 537   } 537   }
HITCBC 538   1 )(hash_task(password_, rounds_, ver_)); 538   1 )(hash_task(password_, rounds_, ver_));
HITCBC 539   1 } 539   1 }
540   540  
HITCBC 541   1 result await_resume() 541   1 result await_resume()
542   { 542   {
HITCBC 543   1 if(ep_) 543   1 if(ep_)
MISUBC 544   std::rethrow_exception(ep_); 544   std::rethrow_exception(ep_);
HITCBC 545   1 return result_; 545   1 return result_;
546   } 546   }
547   }; 547   };
548   548  
549   struct compare_async_op 549   struct compare_async_op
550   { 550   {
551   password_buf password_; 551   password_buf password_;
552   hash_buf hash_str_; 552   hash_buf hash_str_;
553   bool result_ = false; 553   bool result_ = false;
554   std::exception_ptr ep_; 554   std::exception_ptr ep_;
555   capy::continuation cont_; 555   capy::continuation cont_;
556   556  
HITCBC 557   3 bool await_ready() const noexcept 557   3 bool await_ready() const noexcept
558   { 558   {
HITCBC 559   3 return false; 559   3 return false;
560   } 560   }
561   561  
HITCBC 562   3 void await_suspend( 562   3 void await_suspend(
563   std::coroutine_handle<void> cont, 563   std::coroutine_handle<void> cont,
564   capy::io_env const* env) 564   capy::io_env const* env)
565   { 565   {
HITCBC 566   3 cont_.h = cont; 566   3 cont_.h = cont;
HITCBC 567   3 auto caller_ex = env->executor; 567   3 auto caller_ex = env->executor;
HITCBC 568   3 auto& pool = capy::get_system_context(); 568   3 auto& pool = capy::get_system_context();
HITCBC 569   3 auto sys_ex = pool.get_executor(); 569   3 auto sys_ex = pool.get_executor();
HITCBC 570   3 capy::run_async(sys_ex, 570   3 capy::run_async(sys_ex,
HITCBC 571   2 [this, caller_ex] 571   2 [this, caller_ex]
572   (bool ok) mutable 572   (bool ok) mutable
573   { 573   {
HITCBC 574   2 result_ = ok; 574   2 result_ = ok;
HITCBC 575   2 caller_ex.dispatch(cont_).resume(); 575   2 caller_ex.dispatch(cont_).resume();
HITCBC 576   2 }, 576   2 },
HITCBC 577   1 [this, caller_ex] 577   1 [this, caller_ex]
578   (std::exception_ptr ep) mutable 578   (std::exception_ptr ep) mutable
579   { 579   {
HITCBC 580   1 ep_ = ep; 580   1 ep_ = ep;
HITCBC 581   1 caller_ex.dispatch(cont_).resume(); 581   1 caller_ex.dispatch(cont_).resume();
HITCBC 582   1 } 582   1 }
HITCBC 583   3 )(compare_task(password_, hash_str_)); 583   3 )(compare_task(password_, hash_str_));
HITCBC 584   3 } 584   3 }
585   585  
HITCBC 586   3 bool await_resume() 586   3 bool await_resume()
587   { 587   {
HITCBC 588   3 if(ep_) 588   3 if(ep_)
HITCBC 589   1 std::rethrow_exception(ep_); 589   1 std::rethrow_exception(ep_);
HITCBC 590   2 return result_; 590   2 return result_;
591   } 591   }
592   }; 592   };
593   593  
594   } // detail 594   } // detail
595   595  
596   /** Hash a password asynchronously on the system thread pool. 596   /** Hash a password asynchronously on the system thread pool.
597   597  
598   Returns an awaitable that offloads the CPU-intensive 598   Returns an awaitable that offloads the CPU-intensive
599   bcrypt work to the system thread pool, then resumes 599   bcrypt work to the system thread pool, then resumes
600   the caller on their original executor. Modeled after 600   the caller on their original executor. Modeled after
601   Express.js: `await bcrypt.hash(password, 12)`. 601   Express.js: `await bcrypt.hash(password, 12)`.
602   602  
603   @par Example 603   @par Example
604   @code 604   @code
605   bcrypt::result r = co_await bcrypt::hash_async("my_password", 12); 605   bcrypt::result r = co_await bcrypt::hash_async("my_password", 12);
606   @endcode 606   @endcode
607   607  
608   @param password The password to hash. 608   @param password The password to hash.
609   609  
610   @param rounds Cost factor. Each increment doubles the work. 610   @param rounds Cost factor. Each increment doubles the work.
611   611  
612   @param ver Hash version to use. 612   @param ver Hash version to use.
613   613  
614   @return An awaitable yielding `result`. 614   @return An awaitable yielding `result`.
615   615  
616   @throws std::invalid_argument if rounds is out of range. 616   @throws std::invalid_argument if rounds is out of range.
617   @throws system_error on RNG failure. 617   @throws system_error on RNG failure.
618   */ 618   */
619   inline 619   inline
620   detail::hash_async_op 620   detail::hash_async_op
HITCBC 621   1 hash_async( 621   1 hash_async(
622   core::string_view password, 622   core::string_view password,
623   unsigned rounds = 10, 623   unsigned rounds = 10,
624   version ver = version::v2b) 624   version ver = version::v2b)
625   { 625   {
HITCBC 626   1 return detail::hash_async_op{ 626   1 return detail::hash_async_op{
627   detail::password_buf(password), 627   detail::password_buf(password),
628   rounds, 628   rounds,
629   ver, 629   ver,
630   {}, 630   {},
631   {}, 631   {},
HITCBC 632   1 {}}; 632   1 {}};
633   } 633   }
634   634  
635   /** Compare a password against a hash asynchronously. 635   /** Compare a password against a hash asynchronously.
636   636  
637   Returns an awaitable that offloads the CPU-intensive 637   Returns an awaitable that offloads the CPU-intensive
638   bcrypt work to the system thread pool, then resumes 638   bcrypt work to the system thread pool, then resumes
639   the caller on their original executor. Modeled after 639   the caller on their original executor. Modeled after
640   Express.js: `await bcrypt.compare(password, hash)`. 640   Express.js: `await bcrypt.compare(password, hash)`.
641   641  
642   @par Example 642   @par Example
643   @code 643   @code
644   bool ok = co_await bcrypt::compare_async("my_password", stored_hash); 644   bool ok = co_await bcrypt::compare_async("my_password", stored_hash);
645   @endcode 645   @endcode
646   646  
647   @param password The plaintext password to check. 647   @param password The plaintext password to check.
648   648  
649   @param hash_str The hash string to compare against. 649   @param hash_str The hash string to compare against.
650   650  
651   @return An awaitable yielding `bool`. 651   @return An awaitable yielding `bool`.
652   652  
653   @throws system_error if the hash is malformed. 653   @throws system_error if the hash is malformed.
654   */ 654   */
655   inline 655   inline
656   detail::compare_async_op 656   detail::compare_async_op
HITCBC 657   3 compare_async( 657   3 compare_async(
658   core::string_view password, 658   core::string_view password,
659   core::string_view hash_str) 659   core::string_view hash_str)
660   { 660   {
HITCBC 661   3 return detail::compare_async_op{ 661   3 return detail::compare_async_op{
662   detail::password_buf(password), 662   detail::password_buf(password),
663   detail::hash_buf(hash_str), 663   detail::hash_buf(hash_str),
664   false, 664   false,
665   {}, 665   {},
HITCBC 666   3 {}}; 666   3 {}};
667   } 667   }
668   668  
669   } // bcrypt 669   } // bcrypt
670   } // http 670   } // http
671   } // boost 671   } // boost
672   672  
673   #endif 673   #endif