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 "pw_json/builder.h"
17 #include "pw_string/type_to_string.h"
18
19 namespace pw {
20 namespace json_impl {
21
22 constexpr char kArray[2] = {'[', ']'};
23 constexpr char kObject[2] = {'{', '}'};
24
WriteString(std::string_view value,char * buffer,size_t remaining)25 constexpr StatusWithSize WriteString(std::string_view value,
26 char* buffer,
27 size_t remaining) {
28 if (value.size() + 1 /* null */ > remaining) {
29 return StatusWithSize::ResourceExhausted();
30 }
31 for (char c : value) {
32 *buffer++ = c;
33 }
34 *buffer = '\0';
35 return StatusWithSize(value.size());
36 }
37
NibbleToHex(uint8_t nibble)38 constexpr char NibbleToHex(uint8_t nibble) {
39 return nibble + (nibble < 10 ? '0' : ('a' - 10));
40 }
41
42 // In accordance with RFC 8259, JSON strings must escape control characters,
43 // quotation marks, and reverse solidus (\). This function copies a string and
44 // escapes these characters as shown in http://www.json.org/string.gif.
45 //
46 // The return value is the number of bytes written to the destination
47 // buffer or -1 if the string does not fit in the destination buffer. Since
48 // escaped characters result in two or six bytes in the output, the return value
49 // won't necessarily equal the number of bytes read from the source buffer.
50 //
51 // The destination buffer is NEVER null-terminated!
52 //
53 // Currently this function ONLY supports ASCII! Bytes ≥128 will be escaped
54 // individually, rather than treated as multibyte Unicode characters.
EscapedStringCopy(char * destination,int copy_limit,std::string_view source)55 constexpr int EscapedStringCopy(char* destination,
56 int copy_limit,
57 std::string_view source) {
58 int destination_index = 0;
59
60 for (char source_char : source) {
61 if (destination_index >= copy_limit) {
62 return -1;
63 }
64
65 char escaped_character = '\0';
66
67 if (source_char >= '\b' && source_char <= '\r' && source_char != '\v') {
68 constexpr char kControlChars[] = {'b', 't', 'n', '?', 'f', 'r'};
69 escaped_character = kControlChars[source_char - '\b'];
70 } else if (source_char == '"' || source_char == '\\') {
71 escaped_character = source_char;
72 } else if (source_char >= ' ' && source_char <= '~') {
73 // This is a printable character; no escaping is needed.
74 destination[destination_index++] = source_char;
75 continue; // Skip the escaping step below.
76 } else {
77 // Escape control characters that haven't already been handled. These take
78 // six bytes to encode (e.g. \u0056).
79 if (copy_limit - destination_index < 6) {
80 return -1;
81 }
82
83 destination[destination_index++] = '\\';
84 destination[destination_index++] = 'u';
85 destination[destination_index++] = '0'; // Only handle ASCII for now
86 destination[destination_index++] = '0';
87 destination[destination_index++] = NibbleToHex((source_char >> 4) & 0x0f);
88 destination[destination_index++] = NibbleToHex(source_char & 0x0f);
89 continue; // Already escaped; skip the single-character escaping step.
90 }
91
92 // Escape the \b, \t, \n, \f, \r, \", or \\ character, if it fits.
93 if (copy_limit - destination_index < 2) {
94 return -1;
95 }
96
97 destination[destination_index++] = '\\';
98 destination[destination_index++] = escaped_character;
99 }
100 return destination_index;
101 }
102
103 // Writes "<value>", escaping special characters; returns true if successful.
104 // Null terminates ONLY if successful.
WriteQuotedString(std::string_view value,char * buffer,size_t buffer_size)105 constexpr StatusWithSize WriteQuotedString(std::string_view value,
106 char* buffer,
107 size_t buffer_size) {
108 constexpr size_t kOverhead = 2 /* quotes */ + 1 /* null */;
109 if (value.size() + kOverhead > buffer_size) {
110 return StatusWithSize::ResourceExhausted();
111 }
112 // If the string might fit, try to copy it. May still run out of room due to
113 // escaping.
114 const int written =
115 EscapedStringCopy(buffer + 1 /* quote */, buffer_size - kOverhead, value);
116 if (written < 0) {
117 return StatusWithSize::ResourceExhausted();
118 }
119
120 buffer[0] = '"'; // open quote
121 buffer[written + 1] = '"'; // close quote
122 buffer[written + 2] = '\0';
123 return StatusWithSize(written + 2); // characters written excludes \0
124 }
125
WriteCharPointer(const char * ptr,char * buffer,size_t buffer_size)126 constexpr StatusWithSize WriteCharPointer(const char* ptr,
127 char* buffer,
128 size_t buffer_size) {
129 if (ptr == nullptr) {
130 return WriteString("null", buffer, buffer_size);
131 }
132 return WriteQuotedString(ptr, buffer, buffer_size);
133 }
134
135 template <typename T>
136 inline constexpr bool kIsJson =
137 std::is_base_of_v<JsonValue, T> || std::is_base_of_v<JsonArray, T> ||
138 std::is_base_of_v<JsonObject, T>;
139
140 template <typename T>
141 struct InvalidJsonType : std::false_type {};
142
143 struct LiteralChars {
144 const char (&open_close)[2];
145 };
146
147 template <typename T>
SerializeJson(const T & value,char * buffer,size_t remaining)148 constexpr StatusWithSize SerializeJson(const T& value,
149 char* buffer,
150 size_t remaining) {
151 if constexpr (kIsJson<T>) { // nested JsonBuilder, JsonArray, JsonObject
152 return WriteString(value, buffer, remaining);
153 } else if constexpr (std::is_same_v<T, LiteralChars>) { // Nested append/add
154 return WriteString(
155 std::string_view(value.open_close, 2), buffer, remaining);
156 } else if constexpr (std::is_null_pointer_v<T> || // nullptr & C strings
157 std::is_same_v<T, char*> ||
158 std::is_same_v<T, const char*>) {
159 return WriteCharPointer(value, buffer, remaining);
160 } else if constexpr (std::is_convertible_v<T, std::string_view>) { // strings
161 return WriteQuotedString(value, buffer, remaining);
162 } else if constexpr (std::is_floating_point_v<T>) {
163 return string::FloatAsIntToString(value, {buffer, remaining});
164 } else if constexpr (std::is_same_v<T, bool>) { // boolean
165 return WriteString(value ? "true" : "false", buffer, remaining);
166 } else if constexpr (std::is_integral_v<T>) { // integers
167 return string::IntToString(value, {buffer, remaining});
168 } else {
169 static_assert(InvalidJsonType<T>(),
170 "JSON values may only be numbers, strings, JSON arrays, JSON "
171 "objects, or null");
172 return StatusWithSize::Internal();
173 }
174 }
175
176 } // namespace json_impl
177
178 // Forward NestedJsonArray, NestedJsonObject, JsonArray, and JsonObject
179 // function calls to JsonBuilder.
180
181 #define PW_JSON_COMMON_INTERFACE_IMPL(name) \
182 constexpr bool name::IsValue() const { \
183 return static_cast<const JsonBuilder*>(this)->IsValue(); \
184 } \
185 constexpr bool name::IsArray() const { \
186 return static_cast<const JsonBuilder*>(this)->IsArray(); \
187 } \
188 constexpr bool name::IsObject() const { \
189 return static_cast<const JsonBuilder*>(this)->IsObject(); \
190 } \
191 constexpr name::operator std::string_view() const { \
192 return static_cast<const JsonBuilder*>(this)->operator std::string_view(); \
193 } \
194 constexpr const char* name::data() const { \
195 return static_cast<const JsonBuilder*>(this)->data(); \
196 } \
197 constexpr size_t name::size() const { \
198 return static_cast<const JsonBuilder*>(this)->size(); \
199 } \
200 constexpr size_t name::max_size() const { \
201 return static_cast<const JsonBuilder*>(this)->max_size(); \
202 } \
203 constexpr bool name::ok() const { \
204 return static_cast<const JsonBuilder*>(this)->ok(); \
205 } \
206 constexpr Status name::status() const { \
207 return static_cast<const JsonBuilder*>(this)->status(); \
208 } \
209 constexpr Status name::last_status() const { \
210 return static_cast<const JsonBuilder*>(this)->last_status(); \
211 } \
212 constexpr void name::clear() { \
213 static_cast<JsonBuilder*>(this)->name##Clear(); \
214 } \
215 constexpr void name::clear_status() { \
216 static_cast<JsonBuilder*>(this)->clear_status(); \
217 } \
218 static_assert(true)
219
220 PW_JSON_COMMON_INTERFACE_IMPL(JsonValue);
221 PW_JSON_COMMON_INTERFACE_IMPL(JsonArray);
222 PW_JSON_COMMON_INTERFACE_IMPL(JsonObject);
223
224 #undef PW_JSON_COMMON_INTERFACE_IMPL
225
226 template <typename T>
Append(const T & value)227 constexpr NestedJsonArray& NestedJsonArray::Append(const T& value) {
228 json_.builder().NestedJsonArrayAppend(value, json_.nesting());
229 return *this;
230 }
AppendNestedArray()231 constexpr NestedJsonArray NestedJsonArray::AppendNestedArray() {
232 return json_.builder().JsonArrayAppendNested(json_impl::kArray,
233 json_.nesting());
234 }
AppendNestedObject()235 constexpr NestedJsonObject NestedJsonArray::AppendNestedObject() {
236 return json_.builder().JsonArrayAppendNested(json_impl::kObject,
237 json_.nesting());
238 }
AddNestedArray(std::string_view key)239 constexpr NestedJsonArray NestedJsonObject::AddNestedArray(
240 std::string_view key) {
241 return json_.builder().JsonObjectAddNested(
242 key, json_impl::kArray, json_.nesting());
243 }
AddNestedObject(std::string_view key)244 constexpr NestedJsonObject NestedJsonObject::AddNestedObject(
245 std::string_view key) {
246 return json_.builder().JsonObjectAddNested(
247 key, json_impl::kObject, json_.nesting());
248 }
249 template <typename T>
Add(std::string_view key,const T & value)250 constexpr NestedJsonObject& NestedJsonObject::Add(std::string_view key,
251 const T& value) {
252 json_.builder().NestedJsonObjectAdd(key, value, json_.nesting());
253 return *this;
254 }
255 template <typename T>
Set(const T & value)256 constexpr Status JsonValue::Set(const T& value) {
257 return static_cast<JsonBuilder*>(this)->JsonValueSet(value);
258 }
259 template <typename T>
Append(const T & value)260 constexpr JsonArray& JsonArray::Append(const T& value) {
261 return static_cast<JsonBuilder*>(this)->JsonArrayAppend(value);
262 }
AppendNestedArray()263 constexpr NestedJsonArray JsonArray::AppendNestedArray() {
264 return static_cast<JsonBuilder*>(this)->JsonArrayAppendNested(
265 json_impl::kArray, {});
266 }
AppendNestedObject()267 constexpr NestedJsonObject JsonArray::AppendNestedObject() {
268 return static_cast<JsonBuilder*>(this)->JsonArrayAppendNested(
269 json_impl::kObject, {});
270 }
271 template <typename Iterable>
Extend(const Iterable & iterable)272 constexpr JsonArray& JsonArray::Extend(const Iterable& iterable) {
273 return static_cast<JsonBuilder*>(this)->JsonArrayExtend(std::cbegin(iterable),
274 std::cend(iterable));
275 }
276 template <typename T, size_t kSize>
Extend(const T (& iterable)[kSize])277 constexpr JsonArray& JsonArray::Extend(const T (&iterable)[kSize]) {
278 return static_cast<JsonBuilder*>(this)->JsonArrayExtend(std::cbegin(iterable),
279 std::cend(iterable));
280 }
281 template <typename T>
Add(std::string_view key,const T & value)282 constexpr JsonObject& JsonObject::Add(std::string_view key, const T& value) {
283 return static_cast<JsonBuilder*>(this)->JsonObjectAdd(key, value);
284 }
AddNestedArray(std::string_view key)285 constexpr NestedJsonArray JsonObject::AddNestedArray(std::string_view key) {
286 return static_cast<JsonBuilder*>(this)->JsonObjectAddNested(
287 key, json_impl::kArray, {});
288 }
AddNestedObject(std::string_view key)289 constexpr NestedJsonObject JsonObject::AddNestedObject(std::string_view key) {
290 return static_cast<JsonBuilder*>(this)->JsonObjectAddNested(
291 key, json_impl::kObject, {});
292 }
293
294 // JsonBuilder function implementations.
295
296 template <typename T>
SetValue(const T & value)297 constexpr Status JsonBuilder::SetValue(const T& value) {
298 return HandleSet(json_impl::SerializeJson(value, buffer_, max_size_ + 1));
299 }
300
update_status(Status new_status)301 constexpr void JsonBuilder::update_status(Status new_status) {
302 last_status_ = new_status.code();
303 if (!new_status.ok() && status().ok()) {
304 status_ = new_status.code();
305 }
306 }
307
308 template <typename T>
JsonValueSet(const T & value)309 constexpr Status JsonBuilder::JsonValueSet(const T& value) {
310 if constexpr (json_impl::kIsJson<T>) {
311 PW_ASSERT(this != &value); // Self-nesting is disallowed.
312 PW_ASSERT(value.IsValue()); // Cannot set JsonValue to an array or object.
313 }
314 return SetValue(value);
315 }
316
317 template <typename T>
JsonArrayAppend(const T & value)318 constexpr JsonArray& JsonBuilder::JsonArrayAppend(const T& value) {
319 if constexpr (json_impl::kIsJson<T>) {
320 PW_ASSERT(this != &value); // Self-nesting is disallowed.
321 }
322
323 const size_t starting_size = size();
324 if (JsonArrayAddElement()) {
325 // The buffer size is remaining() + 1 bytes, but drop the + 1 to leave room
326 // for the closing ].
327 HandleAdd(json_impl::SerializeJson(value, &buffer_[size()], remaining()),
328 starting_size,
329 ']');
330 }
331 return *this;
332 }
333
334 template <typename Iterator>
JsonArrayExtend(Iterator begin,Iterator end)335 constexpr JsonArray& JsonBuilder::JsonArrayExtend(Iterator begin,
336 Iterator end) {
337 const size_t starting_size = size();
338
339 for (Iterator cur = begin; cur != end; ++cur) {
340 const Status status = Append(*cur).last_status();
341 if (!status.ok()) { // Undo changes if there is an issue.
342 json_size_ = starting_size;
343 buffer_[size() - 1] = ']';
344 buffer_[size()] = '\0';
345 break;
346 }
347 }
348 return *this;
349 }
350
351 template <typename T>
JsonObjectAdd(std::string_view key,const T & value)352 constexpr JsonObject& JsonBuilder::JsonObjectAdd(std::string_view key,
353 const T& value) {
354 if constexpr (json_impl::kIsJson<T>) {
355 PW_ASSERT(this != &value); // Self-nesting is disallowed.
356 }
357
358 const size_t starting_size = size();
359 if (JsonObjectAddKey(key)) {
360 // The buffer size is remaining() + 1 bytes, but drop the + 1 to leave room
361 // for the closing }.
362 HandleAdd(json_impl::SerializeJson(value, &buffer_[size()], remaining()),
363 starting_size,
364 '}');
365 }
366 return *this;
367 }
368
JsonArrayAddElement()369 constexpr bool JsonBuilder::JsonArrayAddElement() {
370 PW_ASSERT(IsArray()); // Attempted to append to an object or value
371
372 // Needs space for at least 3 new characters (, 1)
373 if (size() + 3 > max_size()) {
374 update_status(Status::ResourceExhausted());
375 return false;
376 }
377
378 // If this is the first element, just drop the ]. Otherwise, add a comma.
379 if (size() == 2) {
380 json_size_ = 1;
381 } else {
382 buffer_[json_size_ - 1] = ',';
383 buffer_[json_size_++] = ' ';
384 }
385
386 return true;
387 }
388
JsonObjectAddKey(std::string_view key)389 constexpr bool JsonBuilder::JsonObjectAddKey(std::string_view key) {
390 PW_ASSERT(IsObject()); // Attempted add a key-value pair to an array or value
391
392 // Each key needs 7 more characters: (, "": ) plus at least 1 for the value.
393 // The ',' replaces the terminal '}', but a new '}' is placed at the end, so
394 // the total remains 7. The first key could get away with 5, but oh well.
395 if (size() + key.size() + 7 > max_size()) {
396 update_status(Status::ResourceExhausted());
397 return false;
398 }
399
400 // If this is the first key, just drop the }. Otherwise, add a comma.
401 if (size() == 2) {
402 json_size_ = 1;
403 } else {
404 buffer_[json_size_ - 1] = ','; // change the last } to ,
405 buffer_[json_size_++] = ' ';
406 }
407
408 // Buffer size is remaining() + 1, but - 4 for at least ": 0}" after the key.
409 auto written =
410 json_impl::WriteQuotedString(key, &buffer_[json_size_], remaining() - 3);
411 if (!written.ok()) {
412 return false;
413 }
414
415 json_size_ += written.size(); // Now have {"key" or {..., "key"
416 buffer_[json_size_++] = ':';
417 buffer_[json_size_++] = ' ';
418 return true;
419 }
420
JsonArrayAppendNested(const char (& open_close)[2],const json_impl::Nesting & nesting)421 constexpr json_impl::NestedJson JsonBuilder::JsonArrayAppendNested(
422 const char (&open_close)[2], const json_impl::Nesting& nesting) {
423 AddNestedStart(nesting);
424 json_impl::Nesting::Type nesting_within = type();
425 JsonArrayAppend(json_impl::LiteralChars{open_close}); // [..., {}]
426 AddNestedFinish(nesting);
427 return json_impl::NestedJson(
428 *this,
429 last_status().ok()
430 ? nesting.Nest(NestedJsonOffset(nesting), nesting_within)
431 : json_impl::Nesting());
432 }
433
JsonObjectAddNested(std::string_view key,const char (& open_close)[2],const json_impl::Nesting & nesting)434 constexpr json_impl::NestedJson JsonBuilder::JsonObjectAddNested(
435 std::string_view key,
436 const char (&open_close)[2],
437 const json_impl::Nesting& nesting) {
438 AddNestedStart(nesting);
439 json_impl::Nesting::Type nesting_within = type();
440 JsonObjectAdd(key, json_impl::LiteralChars{open_close}); // {..., "key": {}}
441 AddNestedFinish(nesting);
442 return json_impl::NestedJson(
443 *this,
444 last_status().ok()
445 ? nesting.Nest(NestedJsonOffset(nesting), nesting_within)
446 : json_impl::Nesting());
447 }
448
AddNestedStart(const json_impl::Nesting & nesting)449 constexpr void JsonBuilder::AddNestedStart(const json_impl::Nesting& nesting) {
450 // A nested structure must be the last thing in the JSON. Back up to where the
451 // first of the closing ] or } should be, and check from there.
452 PW_ASSERT( // JSON must not have been cleared since nesting.
453 json_size_ >= nesting.offset() + nesting.depth() + 2 /* [] or {} */);
454 PW_ASSERT( // Nested structure must match the expected type
455 buffer_[json_size_ - nesting.depth() - 1] ==
456 buffer_[nesting.offset()] + 2 /* convert [ to ] or { to } */);
457 nesting.CheckNesting(&buffer_[json_size_ - nesting.depth()]);
458
459 buffer_ += nesting.offset();
460 json_size_ -= nesting.offset() + nesting.depth();
461 max_size_ -= nesting.offset() + nesting.depth();
462 }
463
AddNestedFinish(const json_impl::Nesting & nesting)464 constexpr void JsonBuilder::AddNestedFinish(const json_impl::Nesting& nesting) {
465 buffer_ -= nesting.offset();
466 max_size_ += nesting.offset() + nesting.depth();
467
468 json_size_ += nesting.offset();
469 nesting.Terminate(&buffer_[json_size_]);
470 json_size_ += nesting.depth();
471 }
472
473 template <typename T>
NestedJsonArrayAppend(const T & value,const json_impl::Nesting & nesting)474 constexpr void JsonBuilder::NestedJsonArrayAppend(
475 const T& value, const json_impl::Nesting& nesting) {
476 AddNestedStart(nesting);
477 JsonArrayAppend(value);
478 AddNestedFinish(nesting);
479 }
480
481 template <typename T>
NestedJsonObjectAdd(std::string_view key,const T & value,const json_impl::Nesting & nesting)482 constexpr void JsonBuilder::NestedJsonObjectAdd(
483 std::string_view key, const T& value, const json_impl::Nesting& nesting) {
484 AddNestedStart(nesting);
485 JsonObjectAdd(key, value);
486 AddNestedFinish(nesting);
487 }
488
HandleSet(StatusWithSize written)489 constexpr Status JsonBuilder::HandleSet(StatusWithSize written) {
490 if (written.ok()) {
491 json_size_ = written.size();
492 } else {
493 MakeNull();
494 }
495 set_statuses(written.status()); // status is always reset when setting value
496 return last_status();
497 }
498
HandleAdd(StatusWithSize written,size_t starting_size,char terminator)499 constexpr void JsonBuilder::HandleAdd(StatusWithSize written,
500 size_t starting_size,
501 char terminator) {
502 update_status(written.status()); // save room for } or ]
503 if (last_status().ok()) {
504 json_size_ += written.size();
505 } else {
506 json_size_ = starting_size - 1; // make room for closing character
507 }
508
509 buffer_[json_size_++] = terminator;
510 buffer_[json_size_] = '\0';
511 }
512
513 } // namespace pw
514