diff --git a/test/Metadata.cpp b/test/Metadata.cpp index d132e90c2..b0f3c5994 100644 --- a/test/Metadata.cpp +++ b/test/Metadata.cpp @@ -21,6 +21,8 @@ #include #include +#include +#include #include #include @@ -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> parseCBORMetadata(bytes const& _metadata) +{ + try + { + TinyCBORParser parser(_metadata); + map 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; diff --git a/test/Metadata.h b/test/Metadata.h index 4851aa033..4c7a10b31 100644 --- a/test/Metadata.h +++ b/test/Metadata.h @@ -20,6 +20,10 @@ */ #include + +#include + +#include #include 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> parseCBORMetadata(bytes const& _metadata); + /// Expects a serialised metadata JSON and returns true if the /// content is valid metadata. bool isValidMetadata(std::string const& _metadata);