100.00% Lines (13/13) 100.00% Functions (6/6)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Michael Vandeberg 2   // Copyright (c) 2026 Michael Vandeberg
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/corosio 7   // Official repository: https://github.com/cppalliance/corosio
8   // 8   //
9   9  
10   #ifndef BOOST_COROSIO_STREAM_FILE_HPP 10   #ifndef BOOST_COROSIO_STREAM_FILE_HPP
11   #define BOOST_COROSIO_STREAM_FILE_HPP 11   #define BOOST_COROSIO_STREAM_FILE_HPP
12   12  
13   #include <boost/corosio/detail/config.hpp> 13   #include <boost/corosio/detail/config.hpp>
14 - #include <boost/corosio/detail/except.hpp>  
15   #include <boost/corosio/detail/platform.hpp> 14   #include <boost/corosio/detail/platform.hpp>
16   #include <boost/corosio/detail/native_handle.hpp> 15   #include <boost/corosio/detail/native_handle.hpp>
17   #include <boost/corosio/error.hpp> 16   #include <boost/corosio/error.hpp>
18   #include <boost/corosio/file_base.hpp> 17   #include <boost/corosio/file_base.hpp>
19   #include <boost/corosio/io/io_stream.hpp> 18   #include <boost/corosio/io/io_stream.hpp>
20   #include <boost/capy/ex/execution_context.hpp> 19   #include <boost/capy/ex/execution_context.hpp>
21   #include <boost/capy/concept/executor.hpp> 20   #include <boost/capy/concept/executor.hpp>
22   #include <boost/capy/results.hpp> 21   #include <boost/capy/results.hpp>
23   22  
24   #include <concepts> 23   #include <concepts>
25   #include <cstdint> 24   #include <cstdint>
26   #include <filesystem> 25   #include <filesystem>
27   #include <system_error> 26   #include <system_error>
28   27  
29   namespace boost::corosio { 28   namespace boost::corosio {
30   29  
31   /** Reads and writes a file sequentially, from a coroutine. 30   /** Reads and writes a file sequentially, from a coroutine.
32   31  
33   Provides asynchronous read and write operations on a regular 32   Provides asynchronous read and write operations on a regular
34   file with an implicit position that advances after each 33   file with an implicit position that advances after each
35   operation. 34   operation.
36   35  
37   Inherits from @ref io_stream, so `read_some` and `write_some` 36   Inherits from @ref io_stream, so `read_some` and `write_some`
38   are available and work with any algorithm that accepts an 37   are available and work with any algorithm that accepts an
39   `io_stream&`. 38   `io_stream&`.
40   39  
41   On POSIX platforms, file I/O is dispatched to a thread pool 40   On POSIX platforms, file I/O is dispatched to a thread pool
42   (blocking `preadv`/`pwritev`) with completion posted back to 41   (blocking `preadv`/`pwritev`) with completion posted back to
43   the scheduler. On Windows, true overlapped I/O is used via IOCP. 42   the scheduler. On Windows, true overlapped I/O is used via IOCP.
44   43  
45   On Windows, while the file is open, its handle is bound to the 44   On Windows, while the file is open, its handle is bound to the
46   execution context's completion port. Every overlapped call on the 45   execution context's completion port. Every overlapped call on the
47   handle queues a packet to that port. Do not issue your own 46   handle queues a packet to that port. Do not issue your own
48   overlapped I/O on `native_handle()` (`DeviceIoControl`, 47   overlapped I/O on `native_handle()` (`DeviceIoControl`,
49   `ReadFile`) unless the `OVERLAPPED`'s `hEvent` has its low-order 48   `ReadFile`) unless the `OVERLAPPED`'s `hEvent` has its low-order
50   bit set, which suppresses the packet. 49   bit set, which suppresses the packet.
51   50  
52   @par Thread Safety 51   @par Thread Safety
53   Distinct objects: Safe.@n 52   Distinct objects: Safe.@n
54   Shared objects: Unsafe. Only one asynchronous operation 53   Shared objects: Unsafe. Only one asynchronous operation
55   may be in flight at a time. 54   may be in flight at a time.
56   55  
57   @par Example 56   @par Example
58   @par !example stream_file 57   @par !example stream_file
59   */ 58   */
60   class BOOST_COROSIO_DECL stream_file : public io_stream 59   class BOOST_COROSIO_DECL stream_file : public io_stream
61   { 60   {
62   public: 61   public:
63   /** Defines the file operations a platform backend implements. 62   /** Defines the file operations a platform backend implements.
64   63  
65   Backends derive from this to provide file I/O. 64   Backends derive from this to provide file I/O.
66   `read_some` and `write_some` are inherited from 65   `read_some` and `write_some` are inherited from
67   @ref io_stream::implementation. 66   @ref io_stream::implementation.
68   */ 67   */
69   struct implementation : io_stream::implementation 68   struct implementation : io_stream::implementation
70   { 69   {
71   /// Return the platform file descriptor or handle. 70   /// Return the platform file descriptor or handle.
72   virtual native_handle_type native_handle() const noexcept = 0; 71   virtual native_handle_type native_handle() const noexcept = 0;
73   72  
74   /// Cancel pending asynchronous operations. 73   /// Cancel pending asynchronous operations.
75   virtual void cancel() noexcept = 0; 74   virtual void cancel() noexcept = 0;
76   75  
77   /** Return the file size in bytes. 76   /** Return the file size in bytes.
78   77  
79 - @return The current size of the file, in bytes. 78 + @return The error code and the current size, in bytes.
80 -  
81 - @throws std::system_error if the underlying size query fails.  
82   */ 79   */
83 - virtual std::uint64_t size() const = 0; 80 + virtual capy::results<std::uint64_t> size() const noexcept = 0;
84   81  
85   /** Resize the file to @p new_size bytes. 82   /** Resize the file to @p new_size bytes.
86   83  
87   @param new_size The requested size in bytes. 84   @param new_size The requested size in bytes.
88   85  
89   @return The error code, empty on success. 86   @return The error code, empty on success.
90   */ 87   */
91   virtual std::error_code resize(std::uint64_t new_size) noexcept = 0; 88   virtual std::error_code resize(std::uint64_t new_size) noexcept = 0;
92   89  
93   /** Synchronize file data to stable storage. 90   /** Synchronize file data to stable storage.
94   91  
95   @return The error code, empty on success. 92   @return The error code, empty on success.
96   */ 93   */
97   virtual std::error_code sync_data() noexcept = 0; 94   virtual std::error_code sync_data() noexcept = 0;
98   95  
99   /** Synchronize file data and metadata to stable storage. 96   /** Synchronize file data and metadata to stable storage.
100   97  
101   @return The error code, empty on success. 98   @return The error code, empty on success.
102   */ 99   */
103   virtual std::error_code sync_all() noexcept = 0; 100   virtual std::error_code sync_all() noexcept = 0;
104   101  
105   /** Release ownership of the native handle. 102   /** Release ownership of the native handle.
106   103  
107 - @return The native handle, which the caller now owns. 104 + On success the caller owns the returned handle.
108   105  
109 - @throws std::system_error if the file is not open. 106 + @return The error code and the native handle.
110   */ 107   */
111 - virtual native_handle_type release() = 0; 108 + virtual capy::results<native_handle_type> release() noexcept = 0;
112   109  
113   /** Adopt an existing native handle. 110   /** Adopt an existing native handle.
114   111  
115   @param handle The native handle to adopt. The implementation takes 112   @param handle The native handle to adopt. The implementation takes
116   ownership and closes it. 113   ownership and closes it.
117   114  
118   @return The error code, empty on success. 115   @return The error code, empty on success.
119   */ 116   */
120   virtual std::error_code assign(native_handle_type handle) noexcept = 0; 117   virtual std::error_code assign(native_handle_type handle) noexcept = 0;
121   118  
122   /** Move the file position. 119   /** Move the file position.
123   120  
124   @param offset Signed offset from @p origin. 121   @param offset Signed offset from @p origin.
125   @param origin The reference point for the seek. 122   @param origin The reference point for the seek.
126   @return The error code and new absolute position. 123   @return The error code and new absolute position.
127   */ 124   */
128   virtual capy::results<std::uint64_t> 125   virtual capy::results<std::uint64_t>
129   seek(std::int64_t offset, file_base::seek_basis origin) noexcept = 0; 126   seek(std::int64_t offset, file_base::seek_basis origin) noexcept = 0;
130   }; 127   };
131   128  
132   /** Closes the file if open, cancelling any pending operations. 129   /** Closes the file if open, cancelling any pending operations.
133   */ 130   */
134   ~stream_file() override; 131   ~stream_file() override;
135   132  
136   /** Construct from an execution context. 133   /** Construct from an execution context.
137   134  
138   @param ctx The execution context that owns this file. 135   @param ctx The execution context that owns this file.
139   */ 136   */
140   explicit stream_file(capy::execution_context& ctx); 137   explicit stream_file(capy::execution_context& ctx);
141   138  
142   /** Construct from an executor. 139   /** Construct from an executor.
143   140  
144   @param ex The executor whose context owns this file. 141   @param ex The executor whose context owns this file.
145   */ 142   */
146   template<class Ex> 143   template<class Ex>
147   requires(!std::same_as<std::remove_cvref_t<Ex>, stream_file>) && 144   requires(!std::same_as<std::remove_cvref_t<Ex>, stream_file>) &&
148   capy::Executor<Ex> 145   capy::Executor<Ex>
HITCBC 149   2 explicit stream_file(Ex const& ex) : stream_file(ex.context()) 146   2 explicit stream_file(Ex const& ex) : stream_file(ex.context())
150   { 147   {
HITCBC 151   2 } 148   2 }
152   149  
153   /** Transfers ownership of the file resources. 150   /** Transfers ownership of the file resources.
154   */ 151   */
HITCBC 155   6 stream_file(stream_file&& other) noexcept : io_object(std::move(other)) {} 152   6 stream_file(stream_file&& other) noexcept : io_object(std::move(other)) {}
156   153  
157   /** Closes any existing file and transfers ownership. 154   /** Closes any existing file and transfers ownership.
158   155  
159   @return Reference to this object. 156   @return Reference to this object.
160   */ 157   */
HITCBC 161   2 stream_file& operator=(stream_file&& other) noexcept 158   2 stream_file& operator=(stream_file&& other) noexcept
162   { 159   {
HITCBC 163   2 if (this != &other) 160   2 if (this != &other)
164   { 161   {
HITCBC 165   2 close(); 162   2 close();
HITCBC 166   2 h_ = std::move(other.h_); 163   2 h_ = std::move(other.h_);
167   } 164   }
HITCBC 168   2 return *this; 165   2 return *this;
169   } 166   }
170   167  
171   /// Copy construction is disabled; the handle is uniquely owned. 168   /// Copy construction is disabled; the handle is uniquely owned.
172   stream_file(stream_file const&) = delete; 169   stream_file(stream_file const&) = delete;
173   /// Copy assignment is disabled; the handle is uniquely owned. 170   /// Copy assignment is disabled; the handle is uniquely owned.
174   stream_file& operator=(stream_file const&) = delete; 171   stream_file& operator=(stream_file const&) = delete;
175   172  
176   // read_some() inherited from io_read_stream 173   // read_some() inherited from io_read_stream
177   // write_some() inherited from io_write_stream 174   // write_some() inherited from io_write_stream
178   175  
179   /** Open a file. 176   /** Open a file.
180   177  
181   Failures such as a missing file or insufficient permissions 178   Failures such as a missing file or insufficient permissions
182   are expected runtime conditions and are reported through the 179   are expected runtime conditions and are reported through the
183   returned error code. If the file is already open, it is 180   returned error code. If the file is already open, it is
184   closed first. 181   closed first.
185   182  
186   @param path The filesystem path to open. 183   @param path The filesystem path to open.
187   @param mode Bitmask of @ref file_base::flags specifying 184   @param mode Bitmask of @ref file_base::flags specifying
188   access mode and creation behavior. 185   access mode and creation behavior.
189   186  
190   @return The error code, empty on success. 187   @return The error code, empty on success.
191   */ 188   */
192   [[nodiscard]] std::error_code open( 189   [[nodiscard]] std::error_code open(
193   std::filesystem::path const& path, 190   std::filesystem::path const& path,
194   file_base::flags mode = file_base::read_only) noexcept; 191   file_base::flags mode = file_base::read_only) noexcept;
195   192  
196   /** Close the file. 193   /** Close the file.
197   194  
198   Releases file resources. Pending operations complete through the 195   Releases file resources. Pending operations complete through the
199   same path as @ref cancel: one still in flight completes with 196   same path as @ref cancel: one still in flight completes with
200   `errc::operation_canceled`. An operation whose result is already 197   `errc::operation_canceled`. An operation whose result is already
201   decided reports that result. 198   decided reports that result.
202   */ 199   */
203   void close() noexcept; 200   void close() noexcept;
204   201  
205   /** Check if the file is open. 202   /** Check if the file is open.
206   203  
207   @return `true` if the file is open and ready for I/O. 204   @return `true` if the file is open and ready for I/O.
208   */ 205   */
HITCBC 209   3018 bool is_open() const noexcept 206   3058 bool is_open() const noexcept
210   { 207   {
211   #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS) 208   #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
212   return h_ && get().native_handle() != ~native_handle_type(0); 209   return h_ && get().native_handle() != ~native_handle_type(0);
213   #else 210   #else
HITCBC 214   3018 return h_ && get().native_handle() >= 0; 211   3058 return h_ && get().native_handle() >= 0;
215   #endif 212   #endif
216   } 213   }
217   214  
218   /** Cancel pending asynchronous operations. 215   /** Cancel pending asynchronous operations.
219   216  
220   Operations still in flight complete with 217   Operations still in flight complete with
221   `errc::operation_canceled`; an operation whose result is 218   `errc::operation_canceled`; an operation whose result is
222   already decided reports that result. 219   already decided reports that result.
223   */ 220   */
224   void cancel() noexcept; 221   void cancel() noexcept;
225   222  
226   /** Get the native file descriptor or handle. 223   /** Get the native file descriptor or handle.
227   224  
228   @return The native handle, or -1/INVALID_HANDLE_VALUE 225   @return The native handle, or -1/INVALID_HANDLE_VALUE
229   if not open. 226   if not open.
230   */ 227   */
231   native_handle_type native_handle() const noexcept; 228   native_handle_type native_handle() const noexcept;
232   229  
233   /** Return the file size in bytes. 230   /** Return the file size in bytes.
234   231  
235 - @return The file size in bytes. 232 + A closed file reports `errc::bad_file_descriptor`. Other
  233 + failures carry the error code from the operating system. On
  234 + failure the size is zero.
236   235  
237 - @throws std::system_error If the file is not open, or if the 236 + @return The error code and the file size in bytes.
238 - underlying size query fails.  
239   */ 237   */
240 - std::uint64_t size() const; 238 + [[nodiscard]] capy::results<std::uint64_t> size() const noexcept;
241   239  
242   /** Resize the file to @p new_size bytes. 240   /** Resize the file to @p new_size bytes.
243   241  
244   Failures such as insufficient disk space are reported 242   Failures such as insufficient disk space are reported
245   through the returned error code. A closed file reports 243   through the returned error code. A closed file reports
246   `errc::bad_file_descriptor`. 244   `errc::bad_file_descriptor`.
247   245  
248   @param new_size The new file size. 246   @param new_size The new file size.
249   247  
250   @return The error code, empty on success. 248   @return The error code, empty on success.
251   */ 249   */
252   [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept; 250   [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept;
253   251  
254   /** Synchronize file data to stable storage. 252   /** Synchronize file data to stable storage.
255   253  
256   Write-back failures such as device I/O errors surface here 254   Write-back failures such as device I/O errors surface here
257   and are reported through the returned error code. A closed 255   and are reported through the returned error code. A closed
258   file reports `errc::bad_file_descriptor`. 256   file reports `errc::bad_file_descriptor`.
259   257  
260   @return The error code, empty on success. 258   @return The error code, empty on success.
261   */ 259   */
262   [[nodiscard]] std::error_code sync_data() noexcept; 260   [[nodiscard]] std::error_code sync_data() noexcept;
263   261  
264   /** Synchronize file data and metadata to stable storage. 262   /** Synchronize file data and metadata to stable storage.
265   263  
266   Write-back failures such as device I/O errors surface here 264   Write-back failures such as device I/O errors surface here
267   and are reported through the returned error code. A closed 265   and are reported through the returned error code. A closed
268   file reports `errc::bad_file_descriptor`. 266   file reports `errc::bad_file_descriptor`.
269   267  
270   @return The error code, empty on success. 268   @return The error code, empty on success.
271   */ 269   */
272   [[nodiscard]] std::error_code sync_all() noexcept; 270   [[nodiscard]] std::error_code sync_all() noexcept;
273   271  
274   /** Release ownership of the native handle. 272   /** Release ownership of the native handle.
275   273  
276 - The file object becomes not-open. The caller is 274 + On success the file object becomes not-open, and the caller
277 - responsible for closing the returned handle. 275 + is responsible for closing the returned handle.
278   276  
279 - `release()` cancels pending operations first. On Windows, the 277 + `release()` cancels pending operations first. It reports
280 - object keeps the handle and this throws if one is still in 278 + `errc::device_or_resource_busy` if one is still in flight on
281 - flight. It does the same if Windows refuses to detach the 279 + Windows. Where file I/O runs on a thread pool, it reports the
282 - handle from the execution context's completion port. Where 280 + same while a worker thread is already performing one. Call
283 - file I/O runs on a thread pool, it throws likewise while a 281 + `release()` again once the cancelled operations have completed. On
284 - worker thread is already performing an operation. Call 282 + Windows it reports `errc::operation_not_supported` if the
285 - `release()` again once the cancelled operations have 283 + handle cannot be detached from the execution context's
286 - completed. Detaching requires Windows 8.1 or later. 284 + completion port. Detaching requires Windows 8.1 or later.
287   285  
288 - @return The native file descriptor or handle. 286 + A closed file reports `errc::bad_file_descriptor`. On
  287 + failure the object keeps the handle and the returned handle
  288 + is the invalid sentinel ( -1 or `INVALID_HANDLE_VALUE` ), never
  289 + zero.
289   290  
290 - @throws std::system_error `errc::bad_file_descriptor` if the 291 + @return The error code and the native file descriptor or handle.
291 - file is not open, or `errc::device_or_resource_busy` if an  
292 - operation is still using the handle, as described above. On  
293 - Windows, it also throws `errc::operation_not_supported` if  
294 - the handle cannot be detached.  
295   */ 292   */
296 - native_handle_type release(); 293 + [[nodiscard]] capy::results<native_handle_type> release() noexcept;
297   294  
298   /** Adopt an existing native handle. 295   /** Adopt an existing native handle.
299   296  
300   The object must be closed. To replace a held file, `close()` 297   The object must be closed. To replace a held file, `close()`
301   or `release()` it first. On success the object takes 298   or `release()` it first. On success the object takes
302   ownership of @p handle. Handles created elsewhere may be 299   ownership of @p handle. Handles created elsewhere may be
303   unsuitable for asynchronous I/O. `assign()` reports most such 300   unsuitable for asynchronous I/O. `assign()` reports most such
304   failures through the returned error code. 301   failures through the returned error code.
305   302  
306   @param handle The native file descriptor or handle. 303   @param handle The native file descriptor or handle.
307   304  
308   @return An error code describing the outcome. 305   @return An error code describing the outcome.
309   `error::already_open` if this object is open. 306   `error::already_open` if this object is open.
310   `errc::bad_file_descriptor` if @p handle is invalid. 307   `errc::bad_file_descriptor` if @p handle is invalid.
311   `errc::operation_not_supported` if a file object cannot 308   `errc::operation_not_supported` if a file object cannot
312   use it. On Windows, the rejected handles are a pipe, a 309   use it. On Windows, the rejected handles are a pipe, a
313   socket, a console, a directory, a handle opened without 310   socket, a console, a directory, a handle opened without
314   `FILE_FLAG_OVERLAPPED`, or one 311   `FILE_FLAG_OVERLAPPED`, or one
315   already in skip-completion-port-on-success mode. On 312   already in skip-completion-port-on-success mode. On
316   Windows, `errc::invalid_argument` when @p handle is 313   Windows, `errc::invalid_argument` when @p handle is
317   bound to another completion port. Any other failure is 314   bound to another completion port. Any other failure is
318   the code reported by the system. Otherwise, the code is 315   the code reported by the system. Otherwise, the code is
319   empty. 316   empty.
320   317  
321   @par Exception Safety 318   @par Exception Safety
322   Throws nothing. On failure the object is unchanged and the 319   Throws nothing. On failure the object is unchanged and the
323   caller still owns @p handle. 320   caller still owns @p handle.
324   321  
325   @note On POSIX, the rejected descriptors are, in practice, a 322   @note On POSIX, the rejected descriptors are, in practice, a
326   directory, a pipe, a socket, or any other anonymous inode. 323   directory, a pipe, a socket, or any other anonymous inode.
327   Adopt a pipe, a socket, or an anonymous inode into a 324   Adopt a pipe, a socket, or an anonymous inode into a
328   @ref posix_stream_descriptor instead. `assign()` accepts a 325   @ref posix_stream_descriptor instead. `assign()` accepts a
329   non-seekable character device such as a tty. Its first 326   non-seekable character device such as a tty. Its first
330   read or write then fails with `ESPIPE` on the epoll, 327   read or write then fails with `ESPIPE` on the epoll,
331   kqueue, and select I/O backends. 328   kqueue, and select I/O backends.
332   329  
333   @see @ref release 330   @see @ref release
334   */ 331   */
335   [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept; 332   [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept;
336   333  
337   /** Move the file position. 334   /** Move the file position.
338   335  
339   Positions beyond the end of the file are allowed. A 336   Positions beyond the end of the file are allowed. A
340   resulting negative position is reported through the error 337   resulting negative position is reported through the error
341   code, as offsets often originate from file contents. A 338   code, as offsets often originate from file contents. A
342   closed file reports `errc::bad_file_descriptor`. 339   closed file reports `errc::bad_file_descriptor`.
343   340  
344   @param offset Signed offset from @p origin. 341   @param offset Signed offset from @p origin.
345   @param origin The reference point for the seek. 342   @param origin The reference point for the seek.
346   343  
347   @return The error code and new absolute position. 344   @return The error code and new absolute position.
348   */ 345   */
349   [[nodiscard]] capy::results<std::uint64_t> seek( 346   [[nodiscard]] capy::results<std::uint64_t> seek(
350   std::int64_t offset, 347   std::int64_t offset,
351   file_base::seek_basis origin = file_base::seek_set) noexcept; 348   file_base::seek_basis origin = file_base::seek_set) noexcept;
352   349  
353   protected: 350   protected:
354   /// Default-construct (for derived types that initialize `io_object` directly). 351   /// Default-construct (for derived types that initialize `io_object` directly).
HITCBC 355   16 stream_file() noexcept = default; 352   16 stream_file() noexcept = default;
356   353  
357   /** Construct from a pre-built handle (for `native_stream_file`). 354   /** Construct from a pre-built handle (for `native_stream_file`).
358   355  
359   @param h The pre-built handle to adopt. 356   @param h The pre-built handle to adopt.
360   */ 357   */
361   explicit stream_file(handle h) noexcept : io_object(std::move(h)) {} 358   explicit stream_file(handle h) noexcept : io_object(std::move(h)) {}
362   359  
363   private: 360   private:
HITCBC 364   4651 inline implementation& get() const noexcept 361   4716 inline implementation& get() const noexcept
365   { 362   {
HITCBC 366   4651 return *static_cast<implementation*>(h_.get()); 363   4716 return *static_cast<implementation*>(h_.get());
367   } 364   }
368   }; 365   };
369   366  
370   } // namespace boost::corosio 367   } // namespace boost::corosio
371   368  
372   #endif // BOOST_COROSIO_STREAM_FILE_HPP 369   #endif // BOOST_COROSIO_STREAM_FILE_HPP