Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abstract reader/writer issue with non-contigous input data #250

Open
CrustyAuklet opened this issue Jan 4, 2024 · 1 comment
Open

Abstract reader/writer issue with non-contigous input data #250

CrustyAuklet opened this issue Jan 4, 2024 · 1 comment

Comments

@CrustyAuklet
Copy link

So I think this may be related to and/or covered by #208 , but I wanted to open an issue to report the specifics of what i am seeing.

I think the root of the issue is that in the structure CborParserOperations the function pointer transfer_string should advance the cursor but when calling CborError cbor_value_copy_text_string(const CborValue *value, char *buffer, size_t *buflen, CborValue *next) the cursor should be advanced for the output parameter next but not advanced in the in-out parameter value. I think the user data token* is shared between copies of CborValue.

I am working in an embedded system and transitioning some existing code to use a struct netbuff like structure instead of just copying around worst-case sized buffers. To do this i need to move all my code to work on iterators instead of just taking a std::span<uint8_t> (we are on C++20). The iterators to this new structure are bidirectional_iterator, so no random access.

A simple (exposition only) example of the code that fails:

detail::ParserState<InItr, S> cbor_parse;
CborValue it;
cbor_parse.init(data.begin(), data.end(), it);
CborValue it2;
char func_name_buff[10]{};
size_t name_len = 10;
if (!cbor_value_is_text_string(&it)) {
    return BadValue;
}
if (auto err = cbor_value_copy_text_string(&it, func_name_buff, &name_len, &it2) != CborNoError) {
    return err;
}
auto errr = cbor_value_advance(&it);    // ERROR: CborErrorIllegalType
auto type = cbor_value_get_type(&it)    // CborTextStringType, should be NullType after the advance
auto type2 = cbor_value_get_type(&it2)  // CborNullType

I will work on getting a minimal reproducible example into compiler explorer, especially if this is a new case.

@CrustyAuklet
Copy link
Author

CrustyAuklet commented Jan 4, 2024

Getting it into CE looked like a lot of effort, so I just added a test case to my project. Here is the test case code. Only requires tinycbor and catch2. And I guess you could always replace all the catch2 test cases with asserts :)

EDIT: just FYI, I changed cbor_value_copy_text_string to a simple cbor_value_text_string_equals and the result is the same.

#include <catch2/catch_test_macros.hpp>
#include <cbor.h>
#include <cassert>
#include <iterator>
#include <array>
#include <vector>
#include <algorithm>

namespace {

    template <std::input_iterator InItr, std::sentinel_for<InItr> S>
    struct ParserState {
        InItr itr;
        S end;
        CborParser parser_;
        std::array<uint8_t, 16> str_buff_;

        static inline CborParserOperations ops_{
            .can_read_bytes=[](void* token, size_t len) ->bool {
                auto state = static_cast<ParserState *>(token);
                return state->itr != state->end;
            },
            .read_bytes=[](void* token, void* dst, size_t offset, size_t len) ->void* {
                auto* state = static_cast<ParserState *>(token);
                auto* dest = static_cast<uint8_t*>(dst);
                auto local_itr = state->itr;
                std::advance(local_itr, offset);
                std::copy_n(local_itr, len, dest);
                return dst;
            },
            .advance_bytes=[](void *token, size_t len) ->void {
                auto state = static_cast<ParserState *>(token);
                std::advance(state->itr, len);
            },
            .transfer_string=[](void *token, const void **userptr, size_t offset, size_t len) ->CborError {
                auto state = static_cast<ParserState *>(token);
                assert(len <= state->str_buff_.size());
                *userptr = state->str_buff_.data();
                std::advance(state->itr, offset);
                for (int i = 0; i < len; ++i) {
                    if (state->itr == state->end) {
                        return CborErrorUnexpectedEOF;
                    }
                    state->str_buff_[i] = *state->itr++;
                }
                return CborNoError;
            }
        };

        CborError init(InItr b, S e, CborValue& val) {
            itr = b;
            end = e;
            return cbor_parser_init_reader(&ops_, &parser_, &val, this);
        }
    };

} // namespace

TEST_CASE("CBOR stream reader") {
    // [90, "inf", null]
    std::vector<uint8_t> data{0x83,0x18,0x5a,0x63,0x69,0x6e,0x66,0xf6};
    using I = decltype(data.begin());
    using S = decltype(data.end());

    ParserState<I,S> cbor_parse;
    CborValue it;
    REQUIRE(cbor_parse.init(data.begin(), data.end(), it) == CborNoError);

    // make sure we have an array of 3 items as expected, and then enter it
    REQUIRE(cbor_value_is_array(&it));
    size_t sz = 0;
    cbor_value_get_array_length(&it, &sz);
    REQUIRE(sz == 3);
    REQUIRE(cbor_value_enter_container(&it, &it) == CborNoError);

    // extract the first item: integer that should be 90
    REQUIRE(cbor_value_is_integer(&it));
    uint64_t number = 0;
    REQUIRE(cbor_value_get_uint64(&it, &number) == CborNoError);
    REQUIRE(number == 90);

    // Second item should be a string that reads 'inf'
    REQUIRE(cbor_value_advance(&it) == CborNoError);
    REQUIRE(cbor_value_is_text_string(&it));

    char func_name_buff[10]{};
    size_t name_len = 10;
    CborValue it2;
    REQUIRE(cbor_value_copy_text_string(&it, func_name_buff, &name_len, &it2) == CborNoError);
    REQUIRE(name_len == 3);
    REQUIRE(strncmp(func_name_buff, "inf", 3) == 0);

    // after that operation 'it' should still be the string,
    // and 'it2' should point to the next item which is NULL
    REQUIRE(cbor_value_is_text_string(&it));
    REQUIRE(cbor_value_is_null(&it2));

    // Advance the value to the last item, which is a NULL value
    // this is where it all falls apart!
    CHECK(cbor_value_advance(&it) == CborNoError);  // ERROR: CborErrorIllegalType
    CHECK(cbor_value_is_null(&it));  // CborTextStringType, should be NullType!
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant