diff --git a/TODO b/TODO index 1283099a76828400e9f0a4dcfa9f2170564f4f11..107ccc028a2c4633551bb6070f75c5099a52de99 100644 --- a/TODO +++ b/TODO @@ -19,8 +19,8 @@ === Decoder === * Write functions not yet implemented * Add API for stream-decoding strings -* Add API for random-accessing elements of a map * Add API for checking known tags and simple types * (unlikely) Add API for checking the pairing of a tag and the tagged type * Write tests for error conditions * Fuzzy-test the decoder +* Add recursion limit to recursive functions (advance, map_find) diff --git a/src/cbor.h b/src/cbor.h index e37489516c265218c4f85b378df1b49fa4545d5d..75f844c04858702662418e82a3663dfffc4e407d 100644 --- a/src/cbor.h +++ b/src/cbor.h @@ -307,6 +307,8 @@ CBOR_INLINE_API CborError cbor_value_get_map_length(const CborValue *value, size return CborNoError; } +CBOR_API CborError cbor_value_map_find_value(const CborValue *map, const char *string, CborValue *element); + /* Floating point */ CBOR_API CborError cbor_value_get_half_float(const CborValue *value, void *result); CBOR_INLINE_API CborError cbor_value_get_float(const CborValue *value, float *result) diff --git a/src/cborparser.c b/src/cborparser.c index ac44f80205034653016221e376f5bb32e44b681d..f5de1f73aa24fd83191ffc23d648b1eb627d2031 100644 --- a/src/cborparser.c +++ b/src/cborparser.c @@ -654,6 +654,61 @@ CborError cbor_value_text_string_equals(const CborValue *value, const char *stri return iterate_string_chunks(©, CONST_CAST(char *, string), &len, result, NULL, iterate_memcmp); } +/** + * Attempts to find the value in map \a map that corresponds to the text string + * entry \a string. If the item is found, it is stored in \a result. If no item + * is found matching the key, then \a result will contain an element of type + * \ref CborInvalidType. + * + * \note This function may be expensive to execute. + */ +CborError cbor_value_map_find_value(const CborValue *map, const char *string, CborValue *element) +{ + assert(cbor_value_is_map(map)); + size_t len = strlen(string); + CborError err = cbor_value_enter_container(map, element); + if (err) + goto error; + + while (!cbor_value_at_end(element)) { + // find the non-tag so we can compare + err = cbor_value_skip_tag(element); + if (err) + goto error; + if (cbor_value_is_text_string(element)) { + bool equals; + size_t dummyLen = len; + err = iterate_string_chunks(element, CONST_CAST(char *, string), &dummyLen, + &equals, element, iterate_memcmp); + if (err) + goto error; + if (equals) + return preparse_value(element); + } else { + // skip this key + err = cbor_value_advance(element); + if (err) + goto error; + } + + // skip this value + err = cbor_value_skip_tag(element); + if (err) + goto error; + err = cbor_value_advance(element); + if (err) + goto error; + } + + // not found + element->type = CborInvalidType; + return CborNoError; + +error: + element->type = CborInvalidType; + return err; +} + /** * Extracts a half-precision floating point from \a value and stores it in \a * result. diff --git a/tests/parser/tst_parser.cpp b/tests/parser/tst_parser.cpp index 8a981a60100eafca96e03db97edd7d978639eac0..eca3342db66310568edc1a51cf9f24d86f19b45e 100644 --- a/tests/parser/tst_parser.cpp +++ b/tests/parser/tst_parser.cpp @@ -66,6 +66,8 @@ private slots: void stringLength(); void stringCompare_data(); void stringCompare(); + void mapFind_data(); + void mapFind(); }; char toHexUpper(unsigned n) @@ -1002,5 +1004,96 @@ void tst_Parser::stringCompare() compareOneString("\xc1\xc2" + data, string, expected); } +void tst_Parser::mapFind_data() +{ + // Rules: + // we are searching for string "needle" + // if present, the value should be the string "haystack" (with tag 42) + + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<bool>("expected"); + + QTest::newRow("emptymap") << raw("\xa0") << false; + QTest::newRow("_emptymap") << raw("\xbf\xff") << false; + + // maps not containing our items + QTest::newRow("absent-unsigned-unsigned") << raw("\xa1\0\0") << false; + QTest::newRow("absent-taggedunsigned-unsigned") << raw("\xa1\xc0\0\0") << false; + QTest::newRow("absent-unsigned-taggedunsigned") << raw("\xa1\0\xc0\0") << false; + QTest::newRow("absent-taggedunsigned-taggedunsigned") << raw("\xa1\xc0\0\xc0\0") << false; + QTest::newRow("absent-string-unsigned") << raw("\xa1\x68haystack\0") << false; + QTest::newRow("absent-taggedstring-unsigned") << raw("\xa1\xc0\x68haystack\0") << false; + QTest::newRow("absent-string-taggedunsigned") << raw("\xa1\x68haystack\xc0\0") << false; + QTest::newRow("absent-taggedstring-taggedunsigned") << raw("\xa1\xc0\x68haystack\xc0\0") << false; + QTest::newRow("absent-string-string") << raw("\xa1\x68haystack\x66needle") << false; + QTest::newRow("absent-string-taggedstring") << raw("\xa1\x68haystack\xc0\x66needle") << false; + QTest::newRow("absent-taggedstring-string") << raw("\xa1\xc0\x68haystack\x66needle") << false; + QTest::newRow("absent-string-taggedstring") << raw("\xa1\xc0\x68haystack\xc0\x66needle") << false; + + QTest::newRow("absent-string-emptyarray") << raw("\xa1\x68haystack\x80") << false; + QTest::newRow("absent-string-_emptyarray") << raw("\xa1\x68haystack\x9f\xff") << false; + QTest::newRow("absent-string-array1") << raw("\xa1\x68haystack\x81\0") << false; + QTest::newRow("absent-string-array2") << raw("\xa1\x68haystack\x85\0\1\2\3\4") << false; + QTest::newRow("absent-string-array3") << raw("\xa1\x68haystack\x85\x63one\x63two\x65three\x64""four\x64""five") << false; + + QTest::newRow("absent-string-emptymap") << raw("\xa1\x68haystack\xa0") << false; + QTest::newRow("absent-string-_emptymap") << raw("\xa1\x68haystack\xbf\xff") << false; + QTest::newRow("absent-string-map") << raw("\xa1\x68haystack\xa1\x68haystack\x66needle") << false; + QTest::newRow("absent-string-map2") << raw("\xa1\x68haystack\xa1\x68haystack\x66needle\61z\62yx") << false; + + // maps containing our items + QTest::newRow("alone") << raw("\xa1\x66needle\xd8\x2a\x68haystack") << true; + QTest::newRow("tagged") << raw("\xa1\xc1\x66needle\xd8\x2a\x68haystack") << true; + QTest::newRow("doubletagged") << raw("\xa1\xc1\xc2\x66needle\xd8\x2a\x68haystack") << true; + QTest::newRow("chunked") << raw("\xa1\x7f\x66needle\xff\xd8\x2a\x68haystack") << true; + QTest::newRow("chunked*2") << raw("\xa1\x7f\x60\x66needle\xff\xd8\x2a\x68haystack") << true; + QTest::newRow("chunked*2bis") << raw("\xa1\x7f\x66needle\x60\xff\xd8\x2a\x68haystack") << true; + QTest::newRow("chunked*3") << raw("\xa1\x7f\x62ne\x62""ed\x62le\xff\xd8\x2a\x68haystack") << true; + QTest::newRow("chunked*8") << raw("\xa1\x7f\x61n\x61""e\x60\x61""e\x61""d\x60\x62le\x60\xff\xd8\x2a\x68haystack") << true; + + QTest::newRow("1before") << raw("\xa2\x68haystack\x66needle\x66needle\xd8\x2a\x68haystack") << true; + QTest::newRow("tagged-1before") << raw("\xa2\xc1\x68haystack\x66needle\xc1\x66needle\xd8\x2a\x68haystack") << true; + QTest::newRow("doubletagged-1before2") << raw("\xa2\xc1\xc2\x68haystack\x66needle\xc1\xc2\x66needle\xd8\x2a\x68haystack") << true; + + QTest::newRow("arraybefore") << raw("\xa2\x61z\x80\x66needle\xd8\x2a\x68haystack") << true; + QTest::newRow("nestedarraybefore") << raw("\xa2\x61z\x81\x81\0\x66needle\xd8\x2a\x68haystack") << true; + QTest::newRow("arrayarraybefore") << raw("\xa2\x82\1\2\x80\x66needle\xd8\x2a\x68haystack") << true; + + QTest::newRow("mapbefore") << raw("\xa2\x61z\xa0\x66needle\xd8\x2a\x68haystack") << true; + QTest::newRow("nestedmapbefore") << raw("\xa2\x61z\xa1\0\x81\0\x66needle\xd8\x2a\x68haystack") << true; + QTest::newRow("mapmapbefore") << raw("\xa2\xa1\1\2\xa0\x66needle\xd8\x2a\x68haystack") << true; +} + +void tst_Parser::mapFind() +{ + QFETCH(QByteArray, data); + QFETCH(bool, expected); + + CborParser parser; + CborValue value; + CborError err = cbor_parser_init(data.constData(), data.length(), 0, &parser, &value); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + + CborValue element; + err = cbor_value_map_find_value(&value, "needle", &element); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + + if (expected) { + QCOMPARE(int(element.type), int(CborTagType)); + + CborTag tag; + err = cbor_value_get_tag(&element, &tag); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + QCOMPARE(int(tag), 42); + + bool equals; + err = cbor_value_text_string_equals(&element, "haystack", &equals); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + QVERIFY(equals); + } else { + QCOMPARE(int(element.type), int(CborInvalidType)); + } +} + QTEST_MAIN(tst_Parser) #include "tst_parser.moc"