1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15
16 #include <cstddef>
17 #include <type_traits>
18
19 #include "pw_status/status.h"
20
21 namespace pw {
22
23 /// `StatusWithSize` stores a status and an unsigned integer. The integer must
24 /// not exceed `StatusWithSize::max_size()`, which is 134,217,727 (2**27 - 1) on
25 /// 32-bit systems.
26 ///
27 /// `StatusWithSize` is useful for reporting the number of bytes read or written
28 /// in an operation along with the status. For example, a function that writes a
29 /// formatted string may want to report both the number of characters written
30 /// and whether it ran out of space.
31 ///
32 /// `StatusWithSize` is more efficient than its alternatives. It packs a status
33 /// and size into a single word, which can be returned from a function in a
34 /// register. Because they are packed together, the size is limited to
35 /// max_size().
36 ///
37 /// `StatusWithSize`'s alternatives result in larger code size. For example:
38 ///
39 /// 1. Return status, pass size output as a pointer argument.
40 ///
41 /// Requires an additional argument and forces the output argument to the
42 /// stack in order to pass an address, increasing code size.
43 ///
44 /// 2. Return an object with Status and size members.
45 ///
46 /// At least for ARMv7-M, the returned struct is created on the stack,
47 /// which increases code size.
48 ///
49 class _PW_STATUS_NO_DISCARD StatusWithSize {
50 public:
51 static constexpr StatusWithSize Cancelled(size_t size = 0) {
52 return StatusWithSize(Status::Cancelled(), size);
53 }
54 static constexpr StatusWithSize Unknown(size_t size = 0) {
55 return StatusWithSize(Status::Unknown(), size);
56 }
57 static constexpr StatusWithSize InvalidArgument(size_t size = 0) {
58 return StatusWithSize(Status::InvalidArgument(), size);
59 }
60 static constexpr StatusWithSize DeadlineExceeded(size_t size = 0) {
61 return StatusWithSize(Status::DeadlineExceeded(), size);
62 }
63 static constexpr StatusWithSize NotFound(size_t size = 0) {
64 return StatusWithSize(Status::NotFound(), size);
65 }
66 static constexpr StatusWithSize AlreadyExists(size_t size = 0) {
67 return StatusWithSize(Status::AlreadyExists(), size);
68 }
69 static constexpr StatusWithSize PermissionDenied(size_t size = 0) {
70 return StatusWithSize(Status::PermissionDenied(), size);
71 }
72 static constexpr StatusWithSize Unauthenticated(size_t size = 0) {
73 return StatusWithSize(Status::Unauthenticated(), size);
74 }
75 static constexpr StatusWithSize ResourceExhausted(size_t size = 0) {
76 return StatusWithSize(Status::ResourceExhausted(), size);
77 }
78 static constexpr StatusWithSize FailedPrecondition(size_t size = 0) {
79 return StatusWithSize(Status::FailedPrecondition(), size);
80 }
81 static constexpr StatusWithSize Aborted(size_t size = 0) {
82 return StatusWithSize(Status::Aborted(), size);
83 }
84 static constexpr StatusWithSize OutOfRange(size_t size = 0) {
85 return StatusWithSize(Status::OutOfRange(), size);
86 }
87 static constexpr StatusWithSize Unimplemented(size_t size = 0) {
88 return StatusWithSize(Status::Unimplemented(), size);
89 }
90 static constexpr StatusWithSize Internal(size_t size = 0) {
91 return StatusWithSize(Status::Internal(), size);
92 }
93 static constexpr StatusWithSize Unavailable(size_t size = 0) {
94 return StatusWithSize(Status::Unavailable(), size);
95 }
96 static constexpr StatusWithSize DataLoss(size_t size = 0) {
97 return StatusWithSize(Status::DataLoss(), size);
98 }
99
100 /// Creates a `StatusWithSize` with status `OK` and a size of 0.
StatusWithSize()101 explicit constexpr StatusWithSize() : size_(0) {}
102
103 /// Creates a `StatusWithSize` with status `OK` and the provided size.
104 /// `std::enable_if` is used to prevent enum types (e.g. `Status::Code`) from
105 /// being used.
106 template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
StatusWithSize(T size)107 explicit constexpr StatusWithSize(T size) : size_(static_cast<size_t>(size)) {
108 // TODO(hepler): Add debug-only assert that size <= max_size().
109 }
110
111 /// Creates a StatusWithSize with the provided status and size.
StatusWithSize(Status status,size_t size)112 explicit constexpr StatusWithSize(Status status, size_t size)
113 : StatusWithSize((static_cast<size_t>(status.code()) << kStatusShift) |
114 size) {}
115
116 constexpr StatusWithSize(const StatusWithSize&) = default;
117 constexpr StatusWithSize& operator=(const StatusWithSize&) = default;
118
119 /// `Update` s this status and adds to `size`.
120 ///
121 /// The resulting `StatusWithSize` will have a size of `this->size() +
122 /// new_status_with_size.size()`
123 ///
124 /// The resulting status will be `OK` if both statuses are `OK`, otherwise it
125 /// will take on the earliest non-`OK` status.
UpdateAndAdd(StatusWithSize new_status_with_size)126 constexpr void UpdateAndAdd(StatusWithSize new_status_with_size) {
127 Status new_status;
128 if (ok()) {
129 new_status = new_status_with_size.status();
130 } else {
131 new_status = status();
132 }
133 size_t new_size = size() + new_status_with_size.size();
134 *this = StatusWithSize(new_status, new_size);
135 }
136
137 /// Zeroes the size if the status is not `OK`.
ZeroIfNotOk()138 constexpr void ZeroIfNotOk() {
139 if (!ok()) {
140 *this = StatusWithSize(status(), 0);
141 }
142 }
143
144 /// Returns the size. The size is always present, even if `status()` is an
145 /// error.
size()146 [[nodiscard]] constexpr size_t size() const { return size_ & kSizeMask; }
147
148 /// The maximum valid value for size.
max_size()149 [[nodiscard]] static constexpr size_t max_size() { return kSizeMask; }
150
151 /// True if status() == OkStatus().
ok()152 [[nodiscard]] constexpr bool ok() const {
153 return (size_ & kStatusMask) == 0u;
154 }
155
156 /// Ignores any errors. This method does nothing except potentially suppress
157 /// complaints from any tools that are checking that errors are not dropped on
158 /// the floor.
IgnoreError()159 constexpr void IgnoreError() const {}
160
status()161 [[nodiscard]] constexpr Status status() const {
162 return static_cast<Status::Code>((size_ & kStatusMask) >> kStatusShift);
163 }
164
165 // Functions for checking which status the StatusWithSize contains.
IsCancelled()166 [[nodiscard]] constexpr bool IsCancelled() const {
167 return status().IsCancelled();
168 }
IsUnknown()169 [[nodiscard]] constexpr bool IsUnknown() const {
170 return status().IsUnknown();
171 }
IsInvalidArgument()172 [[nodiscard]] constexpr bool IsInvalidArgument() const {
173 return status().IsInvalidArgument();
174 }
IsDeadlineExceeded()175 [[nodiscard]] constexpr bool IsDeadlineExceeded() const {
176 return status().IsDeadlineExceeded();
177 }
IsNotFound()178 [[nodiscard]] constexpr bool IsNotFound() const {
179 return status().IsNotFound();
180 }
IsAlreadyExists()181 [[nodiscard]] constexpr bool IsAlreadyExists() const {
182 return status().IsAlreadyExists();
183 }
IsPermissionDenied()184 [[nodiscard]] constexpr bool IsPermissionDenied() const {
185 return status().IsPermissionDenied();
186 }
IsResourceExhausted()187 [[nodiscard]] constexpr bool IsResourceExhausted() const {
188 return status().IsResourceExhausted();
189 }
IsFailedPrecondition()190 [[nodiscard]] constexpr bool IsFailedPrecondition() const {
191 return status().IsFailedPrecondition();
192 }
IsAborted()193 [[nodiscard]] constexpr bool IsAborted() const {
194 return status().IsAborted();
195 }
IsOutOfRange()196 [[nodiscard]] constexpr bool IsOutOfRange() const {
197 return status().IsOutOfRange();
198 }
IsUnimplemented()199 [[nodiscard]] constexpr bool IsUnimplemented() const {
200 return status().IsUnimplemented();
201 }
IsInternal()202 [[nodiscard]] constexpr bool IsInternal() const {
203 return status().IsInternal();
204 }
IsUnavailable()205 [[nodiscard]] constexpr bool IsUnavailable() const {
206 return status().IsUnavailable();
207 }
IsDataLoss()208 [[nodiscard]] constexpr bool IsDataLoss() const {
209 return status().IsDataLoss();
210 }
IsUnauthenticated()211 [[nodiscard]] constexpr bool IsUnauthenticated() const {
212 return status().IsUnauthenticated();
213 }
214
215 private:
216 static constexpr size_t kStatusBits = 5;
217 static constexpr size_t kSizeMask = ~static_cast<size_t>(0) >> kStatusBits;
218 static constexpr size_t kStatusMask = ~kSizeMask;
219 static constexpr size_t kStatusShift = sizeof(size_t) * 8 - kStatusBits;
220
221 size_t size_;
222 };
223
224 namespace internal {
225
ConvertToStatus(StatusWithSize status_with_size)226 constexpr Status ConvertToStatus(StatusWithSize status_with_size) {
227 return status_with_size.status();
228 }
229
ConvertToValue(StatusWithSize status_with_size)230 constexpr size_t ConvertToValue(StatusWithSize status_with_size) {
231 return status_with_size.size();
232 }
233
ConvertToStatusWithSize(Status status)234 constexpr StatusWithSize ConvertToStatusWithSize(Status status) {
235 return StatusWithSize(status, 0);
236 }
237
ConvertToStatusWithSize(StatusWithSize status_with_size)238 constexpr StatusWithSize ConvertToStatusWithSize(
239 StatusWithSize status_with_size) {
240 return status_with_size;
241 }
242
243 } // namespace internal
244 } // namespace pw
245