Conversation
seidelrj
left a comment
There was a problem hiding this comment.
like Copilot, I'm also confused about n20_cbor_read_iterate_item. It's not in the header either.
Similar to n20_stream_t which offers safe writing to a buffer n20_istream_t offers a safe way to read from a buffer lowering the risk for out of bounds access.
Add primitives for parsing CBOR headers, extracting slices of octet and text strings, and recursively skipping unknown fields.
f9c6e37 to
d6d0e63
Compare
96ca001 to
088e415
Compare
There was a problem hiding this comment.
Pull Request Overview
This PR adds CBOR parsing primitives to support reading and parsing CBOR-encoded data. The changes implement functions for reading CBOR headers and recursively skipping CBOR items, complementing the existing CBOR write functionality.
- Adds
n20_cbor_read_headerfunction to parse CBOR type and value information from an input stream - Adds
n20_cbor_read_skip_itemfunction to recursively skip over CBOR items including nested structures - Comprehensive test coverage for both functions with various CBOR types and edge cases
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| include/nat20/cbor.h | Adds function declarations for the new CBOR reading primitives with documentation |
| src/core/cbor.c | Implements the CBOR header reading and item skipping functions |
| src/core/test/cbor.cpp | Extensive test suite covering various CBOR types, edge cases, and error conditions |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
src/core/cbor.c
Outdated
| *n = 0; | ||
|
|
||
| uint8_t additional_bytes = 1 << (additional_info - 24); | ||
| for (int i = 0; i < additional_bytes; i++) { |
There was a problem hiding this comment.
Variable type mismatch: additional_bytes is uint8_t but i is int in the loop condition. This could cause warnings or unexpected behavior. Change i to uint8_t or size_t for consistency.
| for (int i = 0; i < additional_bytes; i++) { | |
| for (uint8_t i = 0; i < additional_bytes; i++) { |
|
|
||
| switch (type) { | ||
| case n20_cbor_type_array_e: | ||
| for (size_t i = 0; i < n; i++) { |
There was a problem hiding this comment.
Potential integer overflow: n is uint64_t but loop variable i is size_t. On 32-bit systems, this could cause infinite loops if n exceeds SIZE_MAX. Add a bounds check or use a safer iteration approach.
There was a problem hiding this comment.
we have the check above to make sure n is <= SIZE_MAX before we get to this for loop.
| for (size_t i = 0; i < n; i++) { | ||
| if (!n20_cbor_read_skip_item(s)) { | ||
| return false; | ||
| } | ||
| } | ||
| break; | ||
| case n20_cbor_type_map_e: | ||
| for (size_t i = 0; i < n; i++) { |
There was a problem hiding this comment.
Potential integer overflow: n is uint64_t but loop variable i is size_t. On 32-bit systems, this could cause infinite loops if n exceeds SIZE_MAX. Add a bounds check or use a safer iteration approach.
| for (size_t i = 0; i < n; i++) { | |
| if (!n20_cbor_read_skip_item(s)) { | |
| return false; | |
| } | |
| } | |
| break; | |
| case n20_cbor_type_map_e: | |
| for (size_t i = 0; i < n; i++) { | |
| if (n > SIZE_MAX) { | |
| /* Too many items to skip safely on this platform. */ | |
| return false; | |
| } | |
| for (size_t i = 0; i < (size_t)n; i++) { | |
| if (!n20_cbor_read_skip_item(s)) { | |
| return false; | |
| } | |
| } | |
| break; | |
| case n20_cbor_type_map_e: | |
| if (n > SIZE_MAX) { | |
| /* Too many items to skip safely on this platform. */ | |
| return false; | |
| } | |
| for (size_t i = 0; i < (size_t)n; i++) { |
src/core/cbor.c
Outdated
| break; | ||
| case n20_cbor_type_bytes_e: | ||
| case n20_cbor_type_string_e: { | ||
| uint8_t const *slice = n20_istream_get_slice(s, n); |
There was a problem hiding this comment.
Potential integer overflow: n is uint64_t but n20_istream_get_slice likely expects size_t. On 32-bit systems, this could cause truncation or incorrect behavior if n exceeds SIZE_MAX. Add a bounds check before the call.
| uint8_t const *slice = n20_istream_get_slice(s, n); | |
| if (n > SIZE_MAX) { | |
| return false; | |
| } | |
| uint8_t const *slice = n20_istream_get_slice(s, (size_t)n); |
There was a problem hiding this comment.
n20_istream_get_slice will return NULL if the requested size is too large. The NULL check below is the correct behavoir.
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
src/core/cbor.c
Outdated
| if (additional_info < 24 || additional_info > 27) { | ||
| *n = additional_info; | ||
| return true; | ||
| } | ||
|
|
||
| *n = 0; | ||
|
|
||
| uint8_t additional_bytes = 1 << (additional_info - 24); | ||
| for (uint8_t i = 0; i < additional_bytes; i++) { | ||
| uint8_t byte = 0; | ||
| if (!n20_istream_get(s, &byte)) { | ||
| return false; | ||
| } | ||
| *n = (*n << 8) | byte; | ||
| } | ||
|
|
||
| return true; |
There was a problem hiding this comment.
The condition on line 121 will incorrectly handle additional_info values 28-31. These should be treated as special cases (28-30 are invalid, 31 indicates indefinite length), but the current logic assigns them directly to *n which is incorrect.
| if (additional_info < 24 || additional_info > 27) { | |
| *n = additional_info; | |
| return true; | |
| } | |
| *n = 0; | |
| uint8_t additional_bytes = 1 << (additional_info - 24); | |
| for (uint8_t i = 0; i < additional_bytes; i++) { | |
| uint8_t byte = 0; | |
| if (!n20_istream_get(s, &byte)) { | |
| return false; | |
| } | |
| *n = (*n << 8) | byte; | |
| } | |
| return true; | |
| if (additional_info < 24) { | |
| *n = additional_info; | |
| return true; | |
| } else if (additional_info >= 24 && additional_info <= 27) { | |
| *n = 0; | |
| uint8_t additional_bytes = 1 << (additional_info - 24); | |
| for (uint8_t i = 0; i < additional_bytes; i++) { | |
| uint8_t byte = 0; | |
| if (!n20_istream_get(s, &byte)) { | |
| return false; | |
| } | |
| *n = (*n << 8) | byte; | |
| } | |
| return true; | |
| } else if (additional_info >= 28 && additional_info <= 30) { | |
| // Invalid/reserved additional_info values | |
| return false; | |
| } else if (additional_info == 31) { | |
| // Indefinite length | |
| *n = (uint64_t)-1; | |
| return true; | |
| } |
There was a problem hiding this comment.
I think copilot is on to something here. Right now, if you get an additional_data > 27, the current code will mark the header as valid, and return n as a value between 28-31. This will mess up n20_cbor_read_skip_item. E.g., if you had an array type, and additional_data=28, we would say the array had length 28 and skip that many items. We should also return an error on value 31 since we don't support it.
There was a problem hiding this comment.
Yes I saw that. I have to think about this because. I tried to avoid returning error codes here. I may have to rethink this stance.
| case n20_cbor_type_array_e: | ||
| if (n > SIZE_MAX) { | ||
| /* Prevent overflow in the loop counter. */ | ||
| return false; | ||
| } |
There was a problem hiding this comment.
The check n > SIZE_MAX is always false since n is a uint64_t and SIZE_MAX is typically also 64-bit on 64-bit systems. This check should use a more appropriate limit or handle the case where n cannot fit in a size_t for the loop counter.
There was a problem hiding this comment.
These checks are really irrelevant because the loops will break when the buffer underruns, and because there are no zero sized cbor items to skip there is no real chance spin indefinitely.
| if (n > SIZE_MAX) { | ||
| /* Prevent overflow in the loop counter. */ |
There was a problem hiding this comment.
Same issue as with arrays - the check n > SIZE_MAX is ineffective. Additionally, for maps, the actual number of items to skip is 2 * n (key-value pairs), so the overflow check should account for this multiplication.
| if (n > SIZE_MAX) { | |
| /* Prevent overflow in the loop counter. */ | |
| if (n > SIZE_MAX / 2) { | |
| /* Prevent overflow in the loop counter (2 * n items to skip). */ |
There was a problem hiding this comment.
This does not apply, because we are actually skipping two items per iteration. Also, because an item is at least one byte in size, the underlying stream will necessarily underrun before the counter can loop.
| if (n > SIZE_MAX) { | ||
| /* Prevent uncaught truncation. */ | ||
| return false; | ||
| } |
There was a problem hiding this comment.
The same SIZE_MAX comparison issue exists here. This check will not prevent truncation when casting uint64_t to size_t on systems where they are the same size.
src/core/cbor.c
Outdated
| if (additional_info < 24 || additional_info > 27) { | ||
| *n = additional_info; | ||
| return true; | ||
| } | ||
|
|
||
| *n = 0; | ||
|
|
||
| uint8_t additional_bytes = 1 << (additional_info - 24); | ||
| for (uint8_t i = 0; i < additional_bytes; i++) { | ||
| uint8_t byte = 0; | ||
| if (!n20_istream_get(s, &byte)) { | ||
| return false; | ||
| } | ||
| *n = (*n << 8) | byte; | ||
| } | ||
|
|
||
| return true; |
There was a problem hiding this comment.
I think copilot is on to something here. Right now, if you get an additional_data > 27, the current code will mark the header as valid, and return n as a value between 28-31. This will mess up n20_cbor_read_skip_item. E.g., if you had an array type, and additional_data=28, we would say the array had length 28 and skip that many items. We should also return an error on value 31 since we don't support it.
|
|
||
| switch (type) { | ||
| case n20_cbor_type_array_e: | ||
| for (size_t i = 0; i < n; i++) { |
There was a problem hiding this comment.
we have the check above to make sure n is <= SIZE_MAX before we get to this for loop.
LCOV of commit
|
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
src/core/test/cbor.cpp
Outdated
| #include <variant> | ||
| #include <vector> | ||
|
|
||
| #include "gtest/gtest.h" |
There was a problem hiding this comment.
This include is redundant as gtest/gtest.h is already included at line 17. Remove the duplicate include.
| #include "gtest/gtest.h" |
src/core/test/cbor.cpp
Outdated
| #include <sys/types.h> | ||
|
|
||
| #include <cstdint> | ||
| #include <initializer_list> |
There was a problem hiding this comment.
[nitpick] The includes sys/types.h and initializer_list appear to be unused in the visible code. Consider removing them unless they are used elsewhere in the file.
| #include <sys/types.h> | |
| #include <cstdint> | |
| #include <initializer_list> | |
| #include <cstdint> |
src/core/test/cbor.cpp
Outdated
| #include <sys/types.h> | ||
|
|
||
| #include <cstdint> | ||
| #include <initializer_list> |
There was a problem hiding this comment.
[nitpick] The includes sys/types.h and initializer_list appear to be unused in the visible code. Consider removing them unless they are used elsewhere in the file.
| #include <sys/types.h> | |
| #include <cstdint> | |
| #include <initializer_list> | |
| #include <cstdint> |
| if (n > SIZE_MAX) { | ||
| /* Prevent overflow in the loop counter. */ | ||
| return false; | ||
| } |
There was a problem hiding this comment.
This overflow check has the same issue as above. The condition n > SIZE_MAX will never be true on systems where uint64_t and size_t have the same width. Consider using if (n > SIZE_MAX / 2) or implementing a different overflow prevention strategy.
n20_istream_get_slice now returns a boolean and fills an n20_slice_t structure, which allows for more consistent control flow. This patch also adds the n20_istream_get_string_slice variant, which fills an n20_string_slice_t structure instead. Complete test coverage for stream module.
src/core/cbor.c
Outdated
|
|
||
| if (additional_info < 24 || additional_info > 27) { | ||
| if (additional_info > 27) { | ||
| /* Reserved additional info value. And this code does |
There was a problem hiding this comment.
| /* Reserved additional info value. And this code does | |
| /* Reserved additional info value. And this code does not |
| * @brief Alias for @ref n20_istream_s | ||
| */ | ||
| typedef struct n20_istream_s n20_istream_t; | ||
|
|
There was a problem hiding this comment.
| * specified size and returns a true if the stream is in a good state | |
| * specified size and returns true if the stream is in a good state |
| * the slice_out structure will point to the slice of the stream | ||
| * buffer beginning at the current read position and of size @p size. | ||
| * | ||
| * @param s The input stream to read from. |
There was a problem hiding this comment.
| /** @brief Gets a buffer slice from the input stream. | |
| /** @brief Gets a string slice from the input stream. |
Add primitives for parsing CBOR headers, extracting slices of
octet and text strings, and recursively skipping unknown fields.