Avi Drissman | 8ba1bad | 2022-09-13 19:22:36 | [diff] [blame] | 1 | // Copyright 2017 The Chromium Authors |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Adam Langley | e0e46cdf | 2018-10-29 19:23:16 | [diff] [blame] | 5 | #include "components/cbor/reader.h" |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 6 | |
| 7 | #include <math.h> |
Jun Choi | 087c6d2d2 | 2018-01-22 22:54:18 | [diff] [blame] | 8 | |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 9 | #include <limits> |
Anastasiia N | a63c5fb | 2021-11-19 15:11:32 | [diff] [blame] | 10 | #include <map> |
Jun Choi | 07540c6 | 2017-12-21 02:51:43 | [diff] [blame] | 11 | #include <utility> |
| 12 | |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 13 | #include "base/bit_cast.h" |
Hans Wennborg | df87046c | 2020-04-28 11:06:24 | [diff] [blame] | 14 | #include "base/check_op.h" |
| 15 | #include "base/notreached.h" |
Jun Choi | 06ae32d | 2017-12-21 18:52:39 | [diff] [blame] | 16 | #include "base/numerics/checked_math.h" |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 17 | #include "base/numerics/safe_conversions.h" |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 18 | #include "base/strings/string_util.h" |
Adam Langley | e0e46cdf | 2018-10-29 19:23:16 | [diff] [blame] | 19 | #include "components/cbor/constants.h" |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 20 | #include "components/cbor/float_conversions.h" |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 21 | |
Jun Choi | 9f1446c0 | 2017-12-21 23:33:27 | [diff] [blame] | 22 | namespace cbor { |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 23 | |
Chris Palmer | be2d8dc | 2018-09-14 00:31:42 | [diff] [blame] | 24 | namespace constants { |
| 25 | const char kUnsupportedMajorType[] = "Unsupported major type."; |
| 26 | } |
| 27 | |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 28 | namespace { |
| 29 | |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 30 | Value::Type GetMajorType(uint8_t initial_data_byte) { |
| 31 | return static_cast<Value::Type>( |
Jun Choi | 9f1446c0 | 2017-12-21 23:33:27 | [diff] [blame] | 32 | (initial_data_byte & constants::kMajorTypeMask) >> |
| 33 | constants::kMajorTypeBitShift); |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 34 | } |
| 35 | |
| 36 | uint8_t GetAdditionalInfo(uint8_t initial_data_byte) { |
Jun Choi | 9f1446c0 | 2017-12-21 23:33:27 | [diff] [blame] | 37 | return initial_data_byte & constants::kAdditionalInformationMask; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 38 | } |
| 39 | |
Chris Palmer | be2d8dc | 2018-09-14 00:31:42 | [diff] [blame] | 40 | // Error messages that correspond to each of the error codes. There is 1 |
Adam Langley | e0e46cdf | 2018-10-29 19:23:16 | [diff] [blame] | 41 | // exception: we declare |kUnsupportedMajorType| in constants.h in the |
Chris Palmer | be2d8dc | 2018-09-14 00:31:42 | [diff] [blame] | 42 | // `constants` namespace, because we use it in several files. |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 43 | const char kNoError[] = "Successfully deserialized to a CBOR value."; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 44 | const char kUnknownAdditionalInfo[] = |
| 45 | "Unknown additional info format in the first byte."; |
| 46 | const char kIncompleteCBORData[] = |
| 47 | "Prematurely terminated CBOR data byte array."; |
| 48 | const char kIncorrectMapKeyType[] = |
Kouhei Ueno | 2c9411e | 2018-02-02 00:41:06 | [diff] [blame] | 49 | "Specified map key type is not supported by the current implementation."; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 50 | const char kTooMuchNesting[] = "Too much nesting."; |
Chris Palmer | 867100a | 2018-10-19 18:58:46 | [diff] [blame] | 51 | const char kInvalidUTF8[] = |
| 52 | "String encodings other than UTF-8 are not allowed."; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 53 | const char kExtraneousData[] = "Trailing data bytes are not allowed."; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 54 | const char kMapKeyOutOfOrder[] = |
Adam Langley | 565f5a3 | 2018-04-02 22:53:46 | [diff] [blame] | 55 | "Map keys must be strictly monotonically increasing based on byte length " |
| 56 | "and then by byte-wise lexical order."; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 57 | const char kNonMinimalCBOREncoding[] = |
| 58 | "Unsigned integers must be encoded with minimum number of bytes."; |
Jun Choi | 07540c6 | 2017-12-21 02:51:43 | [diff] [blame] | 59 | const char kUnsupportedSimpleValue[] = |
| 60 | "Unsupported or unassigned simple value."; |
| 61 | const char kUnsupportedFloatingPointValue[] = |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 62 | "Floating point numbers are not supported unless the " |
| 63 | "`allow_floating_point` configuration option is set."; |
Jun Choi | 06ae32d | 2017-12-21 18:52:39 | [diff] [blame] | 64 | const char kOutOfRangeIntegerValue[] = |
| 65 | "Integer values must be between INT64_MIN and INT64_MAX."; |
Anastasiia N | a63c5fb | 2021-11-19 15:11:32 | [diff] [blame] | 66 | const char kMapKeyDuplicate[] = "Duplicate map keys are not allowed."; |
Adam Langley | dc341a3 | 2018-04-04 17:23:41 | [diff] [blame] | 67 | const char kUnknownError[] = "An unknown error occured."; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 68 | |
| 69 | } // namespace |
| 70 | |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 71 | Reader::Config::Config() = default; |
| 72 | Reader::Config::~Config() = default; |
| 73 | |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 74 | Reader::Reader(base::span<const uint8_t> data) |
David Benjamin | f002351 | 2018-08-29 22:30:06 | [diff] [blame] | 75 | : rest_(data), error_code_(DecoderError::CBOR_NO_ERROR) {} |
Sorin Jianu | ad029cb7 | 2024-10-04 03:35:55 | [diff] [blame] | 76 | Reader::~Reader() = default; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 77 | |
| 78 | // static |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 79 | std::optional<Value> Reader::Read(base::span<uint8_t const> data, |
| 80 | DecoderError* error_code_out, |
| 81 | int max_nesting_level) { |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 82 | Config config; |
| 83 | config.error_code_out = error_code_out; |
| 84 | config.max_nesting_level = max_nesting_level; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 85 | |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 86 | return Read(data, config); |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 87 | } |
| 88 | |
Kouhei Ueno | 7367cae6 | 2018-02-20 03:03:09 | [diff] [blame] | 89 | // static |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 90 | std::optional<Value> Reader::Read(base::span<uint8_t const> data, |
| 91 | size_t* num_bytes_consumed, |
| 92 | DecoderError* error_code_out, |
| 93 | int max_nesting_level) { |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 94 | DCHECK(num_bytes_consumed); |
| 95 | |
| 96 | Config config; |
| 97 | config.num_bytes_consumed = num_bytes_consumed; |
| 98 | config.error_code_out = error_code_out; |
| 99 | config.max_nesting_level = max_nesting_level; |
| 100 | |
| 101 | return Read(data, config); |
| 102 | } |
| 103 | |
| 104 | // static |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 105 | std::optional<Value> Reader::Read(base::span<uint8_t const> data, |
| 106 | const Config& config) { |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 107 | Reader reader(data); |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 108 | std::optional<Value> value = |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 109 | reader.DecodeCompleteDataItem(config, config.max_nesting_level); |
Kouhei Ueno | 7367cae6 | 2018-02-20 03:03:09 | [diff] [blame] | 110 | |
Chris Palmer | 867100a | 2018-10-19 18:58:46 | [diff] [blame] | 111 | auto error = reader.GetErrorCode(); |
| 112 | const bool success = value.has_value(); |
| 113 | DCHECK_EQ(success, error == DecoderError::CBOR_NO_ERROR); |
Adam Langley | dc341a3 | 2018-04-04 17:23:41 | [diff] [blame] | 114 | |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 115 | if (config.num_bytes_consumed) { |
| 116 | *config.num_bytes_consumed = |
| 117 | success ? data.size() - reader.num_bytes_remaining() : 0; |
| 118 | } else if (success && reader.num_bytes_remaining() > 0) { |
| 119 | error = DecoderError::EXTRANEOUS_DATA; |
| 120 | value.reset(); |
Chris Palmer | 867100a | 2018-10-19 18:58:46 | [diff] [blame] | 121 | } |
Adam Langley | dc341a3 | 2018-04-04 17:23:41 | [diff] [blame] | 122 | |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 123 | if (config.error_code_out) { |
| 124 | *config.error_code_out = error; |
| 125 | } |
| 126 | |
Chris Palmer | 867100a | 2018-10-19 18:58:46 | [diff] [blame] | 127 | return value; |
Kouhei Ueno | 7367cae6 | 2018-02-20 03:03:09 | [diff] [blame] | 128 | } |
| 129 | |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 130 | std::optional<Value> Reader::DecodeCompleteDataItem(const Config& config, |
| 131 | int max_nesting_level) { |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 132 | if (max_nesting_level < 0 || max_nesting_level > kCBORMaxDepth) { |
| 133 | error_code_ = DecoderError::TOO_MUCH_NESTING; |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 134 | return std::nullopt; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 135 | } |
| 136 | |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 137 | std::optional<DataItemHeader> header = DecodeDataItemHeader(); |
Chris Palmer | 867100a | 2018-10-19 18:58:46 | [diff] [blame] | 138 | if (!header.has_value()) { |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 139 | return std::nullopt; |
Chris Palmer | 867100a | 2018-10-19 18:58:46 | [diff] [blame] | 140 | } |
Kouhei Ueno | 8abc303 | 2018-02-20 05:36:19 | [diff] [blame] | 141 | |
| 142 | switch (header->type) { |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 143 | case Value::Type::UNSIGNED: |
Kouhei Ueno | 8abc303 | 2018-02-20 05:36:19 | [diff] [blame] | 144 | return DecodeValueToUnsigned(header->value); |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 145 | case Value::Type::NEGATIVE: |
Kouhei Ueno | 8abc303 | 2018-02-20 05:36:19 | [diff] [blame] | 146 | return DecodeValueToNegative(header->value); |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 147 | case Value::Type::BYTE_STRING: |
Kouhei Ueno | 8abc303 | 2018-02-20 05:36:19 | [diff] [blame] | 148 | return ReadByteStringContent(*header); |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 149 | case Value::Type::STRING: |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 150 | return ReadStringContent(*header, config); |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 151 | case Value::Type::ARRAY: |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 152 | return ReadArrayContent(*header, config, max_nesting_level); |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 153 | case Value::Type::MAP: |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 154 | return ReadMapContent(*header, config, max_nesting_level); |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 155 | case Value::Type::SIMPLE_VALUE: |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 156 | case Value::Type::FLOAT_VALUE: |
| 157 | // Floating point values also go here since they are also type 7. |
| 158 | return DecodeToSimpleValueOrFloat(*header, config); |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 159 | case Value::Type::TAG: // We explicitly don't support TAG. |
| 160 | case Value::Type::NONE: |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 161 | case Value::Type::INVALID_UTF8: |
Kouhei Ueno | 8abc303 | 2018-02-20 05:36:19 | [diff] [blame] | 162 | break; |
| 163 | } |
| 164 | |
| 165 | error_code_ = DecoderError::UNSUPPORTED_MAJOR_TYPE; |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 166 | return std::nullopt; |
Kouhei Ueno | 8abc303 | 2018-02-20 05:36:19 | [diff] [blame] | 167 | } |
| 168 | |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 169 | std::optional<Reader::DataItemHeader> Reader::DecodeDataItemHeader() { |
| 170 | const std::optional<uint8_t> initial_byte = ReadByte(); |
David Benjamin | f002351 | 2018-08-29 22:30:06 | [diff] [blame] | 171 | if (!initial_byte) { |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 172 | return std::nullopt; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 173 | } |
| 174 | |
David Benjamin | f002351 | 2018-08-29 22:30:06 | [diff] [blame] | 175 | const auto major_type = GetMajorType(initial_byte.value()); |
| 176 | const uint8_t additional_info = GetAdditionalInfo(initial_byte.value()); |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 177 | |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 178 | std::optional<uint64_t> value = |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 179 | ReadVariadicLengthInteger(major_type, additional_info); |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 180 | return value ? std::make_optional( |
Chris Palmer | be2d8dc | 2018-09-14 00:31:42 | [diff] [blame] | 181 | DataItemHeader{major_type, additional_info, value.value()}) |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 182 | : std::nullopt; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 183 | } |
| 184 | |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 185 | std::optional<uint64_t> Reader::ReadVariadicLengthInteger( |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 186 | Value::Type type, |
Chris Palmer | be2d8dc | 2018-09-14 00:31:42 | [diff] [blame] | 187 | uint8_t additional_info) { |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 188 | uint8_t additional_bytes = 0; |
| 189 | if (additional_info < 24) { |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 190 | return std::make_optional(additional_info); |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 191 | } else if (additional_info == 24) { |
| 192 | additional_bytes = 1; |
| 193 | } else if (additional_info == 25) { |
| 194 | additional_bytes = 2; |
| 195 | } else if (additional_info == 26) { |
| 196 | additional_bytes = 4; |
| 197 | } else if (additional_info == 27) { |
| 198 | additional_bytes = 8; |
| 199 | } else { |
| 200 | error_code_ = DecoderError::UNKNOWN_ADDITIONAL_INFO; |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 201 | return std::nullopt; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 202 | } |
| 203 | |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 204 | const std::optional<base::span<const uint8_t>> bytes = |
David Benjamin | f002351 | 2018-08-29 22:30:06 | [diff] [blame] | 205 | ReadBytes(additional_bytes); |
| 206 | if (!bytes) { |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 207 | return std::nullopt; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 208 | } |
| 209 | |
| 210 | uint64_t int_data = 0; |
David Benjamin | f002351 | 2018-08-29 22:30:06 | [diff] [blame] | 211 | for (const uint8_t b : bytes.value()) { |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 212 | int_data <<= 8; |
David Benjamin | f002351 | 2018-08-29 22:30:06 | [diff] [blame] | 213 | int_data |= b; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 214 | } |
| 215 | |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 216 | if (type == Value::Type::SIMPLE_VALUE && additional_info >= 25 && |
| 217 | additional_info <= 27) { |
| 218 | // This is a floating point value and so `additional_bytes` should not be |
| 219 | // treated as an integer by minimality checking. |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 220 | return std::make_optional(int_data); |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 221 | } |
| 222 | |
Chris Palmer | 867100a | 2018-10-19 18:58:46 | [diff] [blame] | 223 | return IsEncodingMinimal(additional_bytes, int_data) |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 224 | ? std::make_optional(int_data) |
| 225 | : std::nullopt; |
Jun Choi | 06ae32d | 2017-12-21 18:52:39 | [diff] [blame] | 226 | } |
| 227 | |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 228 | std::optional<Value> Reader::DecodeValueToNegative(uint64_t value) { |
Jun Choi | 06ae32d | 2017-12-21 18:52:39 | [diff] [blame] | 229 | auto negative_value = -base::CheckedNumeric<int64_t>(value) - 1; |
| 230 | if (!negative_value.IsValid()) { |
| 231 | error_code_ = DecoderError::OUT_OF_RANGE_INTEGER_VALUE; |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 232 | return std::nullopt; |
Jun Choi | 06ae32d | 2017-12-21 18:52:39 | [diff] [blame] | 233 | } |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 234 | return Value(static_cast<int64_t>(negative_value.ValueOrDie())); |
Jun Choi | 06ae32d | 2017-12-21 18:52:39 | [diff] [blame] | 235 | } |
| 236 | |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 237 | std::optional<Value> Reader::DecodeValueToUnsigned(uint64_t value) { |
Jun Choi | 06ae32d | 2017-12-21 18:52:39 | [diff] [blame] | 238 | auto unsigned_value = base::CheckedNumeric<int64_t>(value); |
| 239 | if (!unsigned_value.IsValid()) { |
| 240 | error_code_ = DecoderError::OUT_OF_RANGE_INTEGER_VALUE; |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 241 | return std::nullopt; |
Jun Choi | 06ae32d | 2017-12-21 18:52:39 | [diff] [blame] | 242 | } |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 243 | return Value(static_cast<int64_t>(unsigned_value.ValueOrDie())); |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 244 | } |
| 245 | |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 246 | std::optional<Value> Reader::DecodeToSimpleValueOrFloat( |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 247 | const DataItemHeader& header, |
| 248 | const Config& config) { |
Adam Langley | bdc2b69 | 2018-04-05 15:24:04 | [diff] [blame] | 249 | // ReadVariadicLengthInteger provides this bound. |
| 250 | CHECK_LE(header.additional_info, 27); |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 251 | // Floating point numbers. |
Adam Langley | bdc2b69 | 2018-04-05 15:24:04 | [diff] [blame] | 252 | if (header.additional_info > 24) { |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 253 | if (header.additional_info >= 28) { |
| 254 | error_code_ = DecoderError::UNSUPPORTED_SIMPLE_VALUE; |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 255 | return std::nullopt; |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 256 | } |
| 257 | if (!config.allow_floating_point) { |
| 258 | error_code_ = DecoderError::UNSUPPORTED_FLOATING_POINT_VALUE; |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 259 | return std::nullopt; |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 260 | } |
| 261 | |
| 262 | switch (header.additional_info) { |
| 263 | case 25: |
| 264 | return Value(DecodeHalfPrecisionFloat(header.value)); |
| 265 | case 26: { |
| 266 | double result = |
| 267 | base::bit_cast<float>(static_cast<uint32_t>(header.value)); |
| 268 | if (!std::isfinite(result) || |
| 269 | result == |
| 270 | DecodeHalfPrecisionFloat(EncodeHalfPrecisionFloat(result))) { |
| 271 | // This could have been encoded as a 16 bit float. |
| 272 | // Note that we use `isfinite()` here to handle NaN since infinity |
| 273 | // and NaN can both be encoded in 16 bits but NaN doesn't compare |
| 274 | // with equality. |
| 275 | error_code_ = DecoderError::NON_MINIMAL_CBOR_ENCODING; |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 276 | return std::nullopt; |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 277 | } |
| 278 | return Value(result); |
| 279 | } |
| 280 | case 27: { |
| 281 | double result = base::bit_cast<double>(header.value); |
| 282 | float result_32 = result; |
| 283 | if (result == result_32) { |
| 284 | // This could have been encoded as a 32 bit float. |
| 285 | error_code_ = DecoderError::NON_MINIMAL_CBOR_ENCODING; |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 286 | return std::nullopt; |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 287 | } |
| 288 | return Value(result); |
| 289 | } |
| 290 | default: |
Peter Boström | 77d2135 | 2024-11-13 22:26:11 | [diff] [blame] | 291 | NOTREACHED(); |
Russ Hamilton | 385541b | 2023-07-06 00:30:38 | [diff] [blame] | 292 | } |
Jun Choi | 07540c6 | 2017-12-21 02:51:43 | [diff] [blame] | 293 | } |
| 294 | |
Adam Langley | bdc2b69 | 2018-04-05 15:24:04 | [diff] [blame] | 295 | // Since |header.additional_info| <= 24, ReadVariadicLengthInteger also |
| 296 | // provides this bound for |header.value|. |
Kouhei Ueno | 8abc303 | 2018-02-20 05:36:19 | [diff] [blame] | 297 | CHECK_LE(header.value, 255u); |
Adam Langley | ccb6298 | 2018-04-05 17:44:46 | [diff] [blame] | 298 | // |SimpleValue| is an enum class and so the underlying type is specified to |
| 299 | // be |int|. So this cast is safe. |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 300 | Value::SimpleValue possibly_unsupported_simple_value = |
| 301 | static_cast<Value::SimpleValue>(static_cast<int>(header.value)); |
Jun Choi | 07540c6 | 2017-12-21 02:51:43 | [diff] [blame] | 302 | switch (possibly_unsupported_simple_value) { |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 303 | case Value::SimpleValue::FALSE_VALUE: |
| 304 | case Value::SimpleValue::TRUE_VALUE: |
| 305 | case Value::SimpleValue::NULL_VALUE: |
| 306 | case Value::SimpleValue::UNDEFINED: |
| 307 | return Value(possibly_unsupported_simple_value); |
Jun Choi | 07540c6 | 2017-12-21 02:51:43 | [diff] [blame] | 308 | } |
| 309 | |
| 310 | error_code_ = DecoderError::UNSUPPORTED_SIMPLE_VALUE; |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 311 | return std::nullopt; |
Jun Choi | 07540c6 | 2017-12-21 02:51:43 | [diff] [blame] | 312 | } |
| 313 | |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 314 | std::optional<Value> Reader::ReadStringContent( |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 315 | const Reader::DataItemHeader& header, |
| 316 | const Config& config) { |
Kouhei Ueno | 8abc303 | 2018-02-20 05:36:19 | [diff] [blame] | 317 | uint64_t num_bytes = header.value; |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 318 | const std::optional<base::span<const uint8_t>> bytes = ReadBytes(num_bytes); |
David Benjamin | f002351 | 2018-08-29 22:30:06 | [diff] [blame] | 319 | if (!bytes) { |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 320 | return std::nullopt; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 321 | } |
| 322 | |
David Benjamin | f002351 | 2018-08-29 22:30:06 | [diff] [blame] | 323 | std::string cbor_string(bytes->begin(), bytes->end()); |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 324 | if (base::IsStringUTF8(cbor_string)) { |
| 325 | return Value(std::move(cbor_string)); |
| 326 | } |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 327 | |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 328 | if (config.allow_invalid_utf8) { |
| 329 | return Value(*bytes, Value::Type::INVALID_UTF8); |
| 330 | } |
| 331 | |
| 332 | error_code_ = DecoderError::INVALID_UTF8; |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 333 | return std::nullopt; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 334 | } |
| 335 | |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 336 | std::optional<Value> Reader::ReadByteStringContent( |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 337 | const Reader::DataItemHeader& header) { |
Kouhei Ueno | 8abc303 | 2018-02-20 05:36:19 | [diff] [blame] | 338 | uint64_t num_bytes = header.value; |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 339 | const std::optional<base::span<const uint8_t>> bytes = ReadBytes(num_bytes); |
David Benjamin | f002351 | 2018-08-29 22:30:06 | [diff] [blame] | 340 | if (!bytes) { |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 341 | return std::nullopt; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 342 | } |
| 343 | |
David Benjamin | f002351 | 2018-08-29 22:30:06 | [diff] [blame] | 344 | std::vector<uint8_t> cbor_byte_string(bytes->begin(), bytes->end()); |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 345 | return Value(std::move(cbor_byte_string)); |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 346 | } |
| 347 | |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 348 | std::optional<Value> Reader::ReadArrayContent( |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 349 | const Reader::DataItemHeader& header, |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 350 | const Config& config, |
Kouhei Ueno | 8abc303 | 2018-02-20 05:36:19 | [diff] [blame] | 351 | int max_nesting_level) { |
Chris Palmer | 867100a | 2018-10-19 18:58:46 | [diff] [blame] | 352 | const uint64_t length = header.value; |
Kouhei Ueno | 1e31362 | 2018-02-22 07:37:27 | [diff] [blame] | 353 | |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 354 | Value::ArrayValue cbor_array; |
Kouhei Ueno | 1e31362 | 2018-02-22 07:37:27 | [diff] [blame] | 355 | for (uint64_t i = 0; i < length; ++i) { |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 356 | std::optional<Value> cbor_element = |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 357 | DecodeCompleteDataItem(config, max_nesting_level - 1); |
Chris Palmer | 867100a | 2018-10-19 18:58:46 | [diff] [blame] | 358 | if (!cbor_element.has_value()) { |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 359 | return std::nullopt; |
Chris Palmer | 867100a | 2018-10-19 18:58:46 | [diff] [blame] | 360 | } |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 361 | cbor_array.push_back(std::move(cbor_element.value())); |
| 362 | } |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 363 | return Value(std::move(cbor_array)); |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 364 | } |
| 365 | |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 366 | std::optional<Value> Reader::ReadMapContent( |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 367 | const Reader::DataItemHeader& header, |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 368 | const Config& config, |
Kouhei Ueno | 8abc303 | 2018-02-20 05:36:19 | [diff] [blame] | 369 | int max_nesting_level) { |
Chris Palmer | 867100a | 2018-10-19 18:58:46 | [diff] [blame] | 370 | const uint64_t length = header.value; |
Kouhei Ueno | 1e31362 | 2018-02-22 07:37:27 | [diff] [blame] | 371 | |
Anastasiia N | a63c5fb | 2021-11-19 15:11:32 | [diff] [blame] | 372 | std::map<Value, Value, Value::Less> cbor_map; |
Kouhei Ueno | 1e31362 | 2018-02-22 07:37:27 | [diff] [blame] | 373 | for (uint64_t i = 0; i < length; ++i) { |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 374 | std::optional<Value> key = |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 375 | DecodeCompleteDataItem(config, max_nesting_level - 1); |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 376 | std::optional<Value> value = |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 377 | DecodeCompleteDataItem(config, max_nesting_level - 1); |
Chris Palmer | 867100a | 2018-10-19 18:58:46 | [diff] [blame] | 378 | if (!key.has_value() || !value.has_value()) { |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 379 | return std::nullopt; |
Chris Palmer | 867100a | 2018-10-19 18:58:46 | [diff] [blame] | 380 | } |
Jun Choi | 98a59e46 | 2017-12-14 23:04:09 | [diff] [blame] | 381 | |
Kouhei Ueno | 2c9411e | 2018-02-02 00:41:06 | [diff] [blame] | 382 | switch (key.value().type()) { |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 383 | case Value::Type::UNSIGNED: |
| 384 | case Value::Type::NEGATIVE: |
| 385 | case Value::Type::STRING: |
| 386 | case Value::Type::BYTE_STRING: |
Kouhei Ueno | 2c9411e | 2018-02-02 00:41:06 | [diff] [blame] | 387 | break; |
Adam Langley | 08718f73 | 2019-04-22 22:21:33 | [diff] [blame] | 388 | case Value::Type::INVALID_UTF8: |
| 389 | error_code_ = DecoderError::INVALID_UTF8; |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 390 | return std::nullopt; |
Kouhei Ueno | 2c9411e | 2018-02-02 00:41:06 | [diff] [blame] | 391 | default: |
| 392 | error_code_ = DecoderError::INCORRECT_MAP_KEY_TYPE; |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 393 | return std::nullopt; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 394 | } |
Anastasiia N | a63c5fb | 2021-11-19 15:11:32 | [diff] [blame] | 395 | if (IsDuplicateKey(key.value(), cbor_map)) |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 396 | return std::nullopt; |
Anastasiia N | a63c5fb | 2021-11-19 15:11:32 | [diff] [blame] | 397 | |
| 398 | if (!config.allow_and_canonicalize_out_of_order_keys && |
| 399 | !IsKeyInOrder(key.value(), cbor_map)) { |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 400 | return std::nullopt; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 401 | } |
| 402 | |
Anastasiia N | a63c5fb | 2021-11-19 15:11:32 | [diff] [blame] | 403 | cbor_map.emplace(std::move(key.value()), std::move(value.value())); |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 404 | } |
Anastasiia N | a63c5fb | 2021-11-19 15:11:32 | [diff] [blame] | 405 | |
| 406 | Value::MapValue map; |
| 407 | map.reserve(cbor_map.size()); |
Alison Gale | b8be952 | 2024-04-16 00:00:31 | [diff] [blame] | 408 | // TODO(crbug.com/40205788): when Chromium switches to C++17, this code can be |
Anastasiia N | a63c5fb | 2021-11-19 15:11:32 | [diff] [blame] | 409 | // optimized using std::map::extract(). |
| 410 | for (auto& it : cbor_map) |
| 411 | map.emplace_hint(map.end(), it.first.Clone(), std::move(it.second)); |
| 412 | return Value(std::move(map)); |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 413 | } |
| 414 | |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 415 | std::optional<uint8_t> Reader::ReadByte() { |
| 416 | const std::optional<base::span<const uint8_t>> bytes = ReadBytes(1); |
| 417 | return bytes ? std::make_optional(bytes.value()[0]) : std::nullopt; |
David Benjamin | f002351 | 2018-08-29 22:30:06 | [diff] [blame] | 418 | } |
| 419 | |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 420 | std::optional<base::span<const uint8_t>> Reader::ReadBytes(uint64_t num_bytes) { |
David Benjamin | f002351 | 2018-08-29 22:30:06 | [diff] [blame] | 421 | if (base::strict_cast<uint64_t>(rest_.size()) < num_bytes) { |
| 422 | error_code_ = DecoderError::INCOMPLETE_CBOR_DATA; |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 423 | return std::nullopt; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 424 | } |
Lukasz Anforowicz | 54cc8a2 | 2024-01-29 23:07:58 | [diff] [blame] | 425 | |
| 426 | // The `uint64_t` => `size_t` conversion below will always succeed |
| 427 | // because the `if` condition above implies that `num_bytes` fits into a |
| 428 | // `size_t`. |
| 429 | size_t size = base::checked_cast<size_t>(num_bytes); |
| 430 | |
| 431 | const base::span<const uint8_t> ret = rest_.first(size); |
| 432 | rest_ = rest_.subspan(size); |
David Benjamin | f002351 | 2018-08-29 22:30:06 | [diff] [blame] | 433 | return ret; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 434 | } |
| 435 | |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 436 | bool Reader::IsEncodingMinimal(uint8_t additional_bytes, uint64_t uint_data) { |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 437 | if ((additional_bytes == 1 && uint_data < 24) || |
| 438 | uint_data <= (1ULL << 8 * (additional_bytes >> 1)) - 1) { |
| 439 | error_code_ = DecoderError::NON_MINIMAL_CBOR_ENCODING; |
| 440 | return false; |
| 441 | } |
| 442 | return true; |
| 443 | } |
| 444 | |
Anastasiia N | a63c5fb | 2021-11-19 15:11:32 | [diff] [blame] | 445 | bool Reader::IsKeyInOrder(const Value& new_key, |
| 446 | const std::map<Value, Value, Value::Less>& map) { |
| 447 | if (map.empty()) { |
Adam Langley | 565f5a3 | 2018-04-02 22:53:46 | [diff] [blame] | 448 | return true; |
| 449 | } |
| 450 | |
Anastasiia N | a63c5fb | 2021-11-19 15:11:32 | [diff] [blame] | 451 | const auto& max_current_key = map.rbegin()->first; |
| 452 | const auto less = map.key_comp(); |
Adam Langley | 565f5a3 | 2018-04-02 22:53:46 | [diff] [blame] | 453 | if (!less(max_current_key, new_key)) { |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 454 | error_code_ = DecoderError::OUT_OF_ORDER_KEY; |
| 455 | return false; |
| 456 | } |
| 457 | return true; |
| 458 | } |
| 459 | |
Anastasiia N | a63c5fb | 2021-11-19 15:11:32 | [diff] [blame] | 460 | bool Reader::IsDuplicateKey(const Value& new_key, |
| 461 | const std::map<Value, Value, Value::Less>& map) { |
| 462 | if (map.find(new_key) == map.end()) { |
| 463 | return false; |
| 464 | } |
| 465 | error_code_ = DecoderError::DUPLICATE_KEY; |
| 466 | return true; |
| 467 | } |
| 468 | |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 469 | // static |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 470 | const char* Reader::ErrorCodeToString(DecoderError error) { |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 471 | switch (error) { |
Daniel Bratell | 50fe929 | 2017-12-11 15:18:54 | [diff] [blame] | 472 | case DecoderError::CBOR_NO_ERROR: |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 473 | return kNoError; |
| 474 | case DecoderError::UNSUPPORTED_MAJOR_TYPE: |
Chris Palmer | be2d8dc | 2018-09-14 00:31:42 | [diff] [blame] | 475 | return constants::kUnsupportedMajorType; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 476 | case DecoderError::UNKNOWN_ADDITIONAL_INFO: |
| 477 | return kUnknownAdditionalInfo; |
| 478 | case DecoderError::INCOMPLETE_CBOR_DATA: |
| 479 | return kIncompleteCBORData; |
| 480 | case DecoderError::INCORRECT_MAP_KEY_TYPE: |
| 481 | return kIncorrectMapKeyType; |
| 482 | case DecoderError::TOO_MUCH_NESTING: |
| 483 | return kTooMuchNesting; |
| 484 | case DecoderError::INVALID_UTF8: |
| 485 | return kInvalidUTF8; |
| 486 | case DecoderError::EXTRANEOUS_DATA: |
| 487 | return kExtraneousData; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 488 | case DecoderError::OUT_OF_ORDER_KEY: |
| 489 | return kMapKeyOutOfOrder; |
| 490 | case DecoderError::NON_MINIMAL_CBOR_ENCODING: |
| 491 | return kNonMinimalCBOREncoding; |
Jun Choi | 07540c6 | 2017-12-21 02:51:43 | [diff] [blame] | 492 | case DecoderError::UNSUPPORTED_SIMPLE_VALUE: |
| 493 | return kUnsupportedSimpleValue; |
| 494 | case DecoderError::UNSUPPORTED_FLOATING_POINT_VALUE: |
| 495 | return kUnsupportedFloatingPointValue; |
Jun Choi | 06ae32d | 2017-12-21 18:52:39 | [diff] [blame] | 496 | case DecoderError::OUT_OF_RANGE_INTEGER_VALUE: |
| 497 | return kOutOfRangeIntegerValue; |
Anastasiia N | a63c5fb | 2021-11-19 15:11:32 | [diff] [blame] | 498 | case DecoderError::DUPLICATE_KEY: |
| 499 | return kMapKeyDuplicate; |
Adam Langley | dc341a3 | 2018-04-04 17:23:41 | [diff] [blame] | 500 | case DecoderError::UNKNOWN_ERROR: |
| 501 | return kUnknownError; |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 502 | default: |
Peter Boström | 77d2135 | 2024-11-13 22:26:11 | [diff] [blame] | 503 | NOTREACHED(); |
Jun Choi | 6d30c4a | 2017-12-09 01:10:32 | [diff] [blame] | 504 | } |
| 505 | } |
| 506 | |
Jun Choi | 9f1446c0 | 2017-12-21 23:33:27 | [diff] [blame] | 507 | } // namespace cbor |