Add tiny CBOR parser

This commit is contained in:
Alex Beregszaszi 2019-03-04 23:43:55 +01:00
parent 12f34c8229
commit 61220eb3e0
2 changed files with 125 additions and 0 deletions

View File

@ -21,6 +21,8 @@
#include <string>
#include <iostream>
#include <libdevcore/Assertions.h>
#include <libdevcore/CommonData.h>
#include <libdevcore/JSON.h>
#include <test/Metadata.h>
@ -59,6 +61,116 @@ string bytecodeSansMetadata(string const& _bytecode)
return toHex(bytecodeSansMetadata(fromHex(_bytecode, WhenError::Throw)));
}
DEV_SIMPLE_EXCEPTION(CBORException);
class TinyCBORParser
{
public:
explicit TinyCBORParser(bytes const& _metadata): m_pos(0), m_metadata(_metadata)
{
assertThrow((m_pos + 1) < _metadata.size(), CBORException, "Input too short.");
}
unsigned mapItemCount()
{
assertThrow(nextType() == MajorType::Map, CBORException, "Fixed-length map expected.");
return readLength();
}
string readKey()
{
return readString();
}
string readValue()
{
switch(nextType())
{
case MajorType::ByteString:
return toHex(readBytes(readLength()));
case MajorType::TextString:
return readString();
case MajorType::SimpleData:
{
unsigned value = nextImmediate();
m_pos++;
if (value == 20)
return "false";
else if (value == 21)
return "true";
else
assertThrow(false, CBORException, "Unsupported simple value (not a boolean).");
}
default:
assertThrow(false, CBORException, "Unsupported value type.");
}
}
private:
enum class MajorType
{
ByteString,
TextString,
Map,
SimpleData
};
MajorType nextType() const
{
unsigned value = (m_metadata.at(m_pos) >> 5) & 0x7;
switch (value)
{
case 2: return MajorType::ByteString;
case 3: return MajorType::TextString;
case 5: return MajorType::Map;
case 7: return MajorType::SimpleData;
default: assertThrow(false, CBORException, "Unsupported major type.");
}
}
unsigned nextImmediate() const { return m_metadata.at(m_pos) & 0x1f; }
unsigned readLength()
{
unsigned length = m_metadata.at(m_pos++) & 0x1f;
if (length < 24)
return length;
if (length == 24)
return m_metadata.at(m_pos++);
// Unsupported length kind. (Only by this parser.)
assertThrow(false, CBORException, string("Unsupported length ") + to_string(length));
}
bytes readBytes(unsigned length)
{
bytes ret{m_metadata.begin() + m_pos, m_metadata.begin() + m_pos + length};
m_pos += length;
return ret;
}
string readString()
{
// Expect a text string.
assertThrow(nextType() == MajorType::TextString, CBORException, "String expected.");
bytes tmp{readBytes(readLength())};
return string{tmp.begin(), tmp.end()};
}
unsigned m_pos;
bytes const& m_metadata;
};
boost::optional<map<string, string>> parseCBORMetadata(bytes const& _metadata)
{
try
{
TinyCBORParser parser(_metadata);
map<string, string> ret;
unsigned count = parser.mapItemCount();
for (unsigned i = 0; i < count; i++)
{
string key = parser.readKey();
string value = parser.readValue();
ret[move(key)] = move(value);
}
return ret;
}
catch (CBORException const&)
{
return {};
}
}
bool isValidMetadata(string const& _metadata)
{
Json::Value metadata;

View File

@ -20,6 +20,10 @@
*/
#include <libdevcore/CommonData.h>
#include <boost/optional.hpp>
#include <map>
#include <string>
namespace dev
@ -37,6 +41,15 @@ bytes bytecodeSansMetadata(bytes const& _bytecode);
/// Throws exception on invalid hex string.
std::string bytecodeSansMetadata(std::string const& _bytecode);
/// Parse CBOR metadata into a map. Expects the input CBOR to be a
/// fixed length map, with each key being a string. The values
/// are parsed as follows:
/// - strings into strings
/// - bytes into hex strings
/// - booleans into "true"/"false" strings
/// - everything else is invalid
boost::optional<std::map<std::string, std::string>> parseCBORMetadata(bytes const& _metadata);
/// Expects a serialised metadata JSON and returns true if the
/// content is valid metadata.
bool isValidMetadata(std::string const& _metadata);