aboutsummaryrefslogtreecommitdiffstats
path: root/vespalib
diff options
context:
space:
mode:
authorArne H Juul <arnej@yahooinc.com>2021-10-12 06:07:06 +0000
committerArne H Juul <arnej@yahooinc.com>2021-10-12 06:12:28 +0000
commita46ddef2850cbce4af3a499a4ef2da3f9720e17d (patch)
tree9b5b30a29313649ce77d0dffeedc693ef619c881 /vespalib
parent9ae8365988df61354b9a87d16cd3ddaff21fb102 (diff)
add "data" format as JSON extension
* when parsing JSON, allow binary data to be input as a hexdump prefixed with the letter "x"; this means that { foo: xFF0011 } will decode to an object where the "foo" field is of type DATA and contains the bytes 255, 0, and 17.
Diffstat (limited to 'vespalib')
-rw-r--r--vespalib/src/tests/slime/slime_json_format_test.cpp39
-rw-r--r--vespalib/src/vespa/vespalib/data/slime/json_format.cpp71
2 files changed, 90 insertions, 20 deletions
diff --git a/vespalib/src/tests/slime/slime_json_format_test.cpp b/vespalib/src/tests/slime/slime_json_format_test.cpp
index 9e1040b36ad..05dca9112d6 100644
--- a/vespalib/src/tests/slime/slime_json_format_test.cpp
+++ b/vespalib/src/tests/slime/slime_json_format_test.cpp
@@ -278,6 +278,36 @@ TEST("decode string") {
EXPECT_EQUAL(std::string("\xf4\x8f\xbf\xbf"), json_string("\\udbff\\udfff"));
}
+TEST_F("decode data", Slime) {
+ EXPECT_TRUE(parse_json("x", f));
+ EXPECT_EQUAL(vespalib::slime::DATA::ID, f.get().type().getId());
+ Memory m = f.get().asData();
+ EXPECT_EQUAL(0u, m.size);
+
+ EXPECT_TRUE(parse_json("x0000", f));
+ EXPECT_EQUAL(vespalib::slime::DATA::ID, f.get().type().getId());
+ m = f.get().asData();
+ EXPECT_EQUAL(2u, m.size);
+ EXPECT_EQUAL(0, m.data[0]);
+ EXPECT_EQUAL(0, m.data[1]);
+
+ EXPECT_TRUE(parse_json("x1234567890abcdefABCDEF", f));
+ EXPECT_EQUAL(vespalib::slime::DATA::ID, f.get().type().getId());
+ m = f.get().asData();
+ EXPECT_EQUAL(11u, m.size);
+ EXPECT_EQUAL((char)0x12, m.data[0]);
+ EXPECT_EQUAL((char)0x34, m.data[1]);
+ EXPECT_EQUAL((char)0x56, m.data[2]);
+ EXPECT_EQUAL((char)0x78, m.data[3]);
+ EXPECT_EQUAL((char)0x90, m.data[4]);
+ EXPECT_EQUAL((char)0xAB, m.data[5]);
+ EXPECT_EQUAL((char)0xCD, m.data[6]);
+ EXPECT_EQUAL((char)0xEF, m.data[7]);
+ EXPECT_EQUAL((char)0xAB, m.data[8]);
+ EXPECT_EQUAL((char)0xCD, m.data[9]);
+ EXPECT_EQUAL((char)0xEF, m.data[10]);
+}
+
TEST_F("decode empty array", Slime) {
EXPECT_TRUE(parse_json("[]", f));
EXPECT_EQUAL(vespalib::slime::ARRAY::ID, f.get().type().getId());
@@ -301,13 +331,18 @@ TEST_F("decode array", Slime) {
}
TEST_F("decode object", Slime) {
- EXPECT_TRUE(parse_json("{\"a\":123,\"b\":0.5,\"c\":\"foo\",\"d\":true}", f));
+ EXPECT_TRUE(parse_json("{\"a\":123,\"b\":0.5,\"c\":\"foo\",\"d\":true,\"e\":xff0011}", f));
EXPECT_EQUAL(vespalib::slime::OBJECT::ID, f.get().type().getId());
- EXPECT_EQUAL(4u, f.get().children());
+ EXPECT_EQUAL(5u, f.get().children());
EXPECT_EQUAL(123.0, f.get()["a"].asDouble());
EXPECT_EQUAL(0.5, f.get()["b"].asDouble());
EXPECT_EQUAL(std::string("foo"), f.get()["c"].asString().make_string());
EXPECT_EQUAL(true, f.get()["d"].asBool());
+ Memory m = f.get()["e"].asData();
+ EXPECT_EQUAL(3u, m.size);
+ EXPECT_EQUAL((char)255, m.data[0]);
+ EXPECT_EQUAL((char)0, m.data[1]);
+ EXPECT_EQUAL((char)17, m.data[2]);
}
TEST_F("decode nesting", Slime) {
diff --git a/vespalib/src/vespa/vespalib/data/slime/json_format.cpp b/vespalib/src/vespa/vespalib/data/slime/json_format.cpp
index b0c66d4cb88..cf01aedb8df 100644
--- a/vespalib/src/vespa/vespalib/data/slime/json_format.cpp
+++ b/vespalib/src/vespa/vespalib/data/slime/json_format.cpp
@@ -17,6 +17,29 @@ namespace vespalib::slime {
namespace {
+inline int fromHexDigit(char digit) {
+ switch(digit) {
+ case '0': return 0;
+ case '1': return 1;
+ case '2': return 2;
+ case '3': return 3;
+ case '4': return 4;
+ case '5': return 5;
+ case '6': return 6;
+ case '7': return 7;
+ case '8': return 8;
+ case '9': return 9;
+ case 'a': case 'A': return 0xA;
+ case 'b': case 'B': return 0xB;
+ case 'c': case 'C': return 0xC;
+ case 'd': case 'D': return 0xD;
+ case 'e': case 'E': return 0xE;
+ case 'f': case 'F': return 0xF;
+ default:
+ return -1;
+ }
+}
+
template <bool COMPACT>
struct JsonEncoder : public ArrayTraverser,
public ObjectTraverser
@@ -223,6 +246,7 @@ struct JsonDecoder {
void decodeObject(Inserter &inserter);
void decodeArray(Inserter &inserter);
void decodeNumber(Inserter &inserter);
+ void decodeData(Inserter &inserter);
void decodeValue(Inserter &inserter) {
skipWhiteSpace();
switch (c) {
@@ -232,6 +256,7 @@ struct JsonDecoder {
case 't': expect("true"); inserter.insertBool(true); return;
case 'f': expect("false"); inserter.insertBool(false); return;
case 'n': expect("null"); inserter.insertNix(); return;
+ case 'x': return decodeData(inserter);
case '-': case '0': case '1': case '2': case '3': case '4': case '5':
case '6': case '7': case '8': case '9': return decodeNumber(inserter);
}
@@ -249,27 +274,12 @@ JsonDecoder::readHexValue(uint32_t len)
{
uint32_t ret = 0;
for (uint32_t i = 0; i < len; ++i) {
- switch (c) {
- case '0': ret = (ret << 4) | 0; break;
- case '1': ret = (ret << 4) | 1; break;
- case '2': ret = (ret << 4) | 2; break;
- case '3': ret = (ret << 4) | 3; break;
- case '4': ret = (ret << 4) | 4; break;
- case '5': ret = (ret << 4) | 5; break;
- case '6': ret = (ret << 4) | 6; break;
- case '7': ret = (ret << 4) | 7; break;
- case '8': ret = (ret << 4) | 8; break;
- case '9': ret = (ret << 4) | 9; break;
- case 'a': case 'A': ret = (ret << 4) | 0xa; break;
- case 'b': case 'B': ret = (ret << 4) | 0xb; break;
- case 'c': case 'C': ret = (ret << 4) | 0xc; break;
- case 'd': case 'D': ret = (ret << 4) | 0xd; break;
- case 'e': case 'E': ret = (ret << 4) | 0xe; break;
- case 'f': case 'F': ret = (ret << 4) | 0xf; break;
- default:
+ int d = fromHexDigit(c);
+ if (d < 0) {
in.fail("invalid hex character");
return 0;
}
+ ret = (ret << 4) | d;
next();
}
return ret;
@@ -413,6 +423,31 @@ JsonDecoder::decodeArray(Inserter &inserter)
expect("]");
}
+void JsonDecoder::decodeData(Inserter &inserter) {
+ vespalib::string data;
+ value.clear();
+ for (;;) {
+ next();
+ value.push_back(c);
+ int hi = fromHexDigit(c);
+ if (hi < 0) {
+ inserter.insertData(data);
+ return;
+ }
+ next();
+ value.push_back(c);
+ int lo = fromHexDigit(c);
+ if (lo < 0) {
+ std::stringstream ss;
+ ss << "error inserting data '" << value << "'. invalid hex dump.";
+ in.fail(ss.str());
+ return;
+ }
+ char byte = (hi << 4) | lo;
+ data.push_back(byte);
+ }
+}
+
static int insertNumber(Inserter &inserter, bool isLong, const vespalib::string &value, char **endp);
void