From 7fc7bab25de36ef617a404d2d1ff8b86a2776c38 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Fri, 4 Sep 2015 16:59:04 +0200 Subject: [PATCH 01/16] remove univalue, prepare for subtree --- src/univalue/gen.cpp | 78 ------- src/univalue/univalue.cpp | 304 ------------------------- src/univalue/univalue.h | 250 -------------------- src/univalue/univalue_escapes.h | 262 --------------------- src/univalue/univalue_read.cpp | 390 -------------------------------- src/univalue/univalue_write.cpp | 134 ----------- 6 files changed, 1418 deletions(-) delete mode 100644 src/univalue/gen.cpp delete mode 100644 src/univalue/univalue.cpp delete mode 100644 src/univalue/univalue.h delete mode 100644 src/univalue/univalue_escapes.h delete mode 100644 src/univalue/univalue_read.cpp delete mode 100644 src/univalue/univalue_write.cpp diff --git a/src/univalue/gen.cpp b/src/univalue/gen.cpp deleted file mode 100644 index abebe886..00000000 --- a/src/univalue/gen.cpp +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2014 BitPay Inc. -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -// -// To re-create univalue_escapes.h: -// $ g++ -o gen gen.cpp -// $ ./gen > univalue_escapes.h -// - -#include -#include -#include -#include "univalue.h" - -using namespace std; - -static bool initEscapes; -static const char *escapes[256]; - -static void initJsonEscape() -{ - escapes[(int)'"'] = "\\\""; - escapes[(int)'\\'] = "\\\\"; - escapes[(int)'/'] = "\\/"; - escapes[(int)'\b'] = "\\b"; - escapes[(int)'\f'] = "\\f"; - escapes[(int)'\n'] = "\\n"; - escapes[(int)'\r'] = "\\r"; - escapes[(int)'\t'] = "\\t"; - - initEscapes = true; -} - -static void outputEscape() -{ - printf( "// Automatically generated file. Do not modify.\n" - "#ifndef BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H\n" - "#define BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H\n" - "static const char *escapes[256] = {\n"); - - for (unsigned int i = 0; i < 256; i++) { - if (!escapes[i]) { - printf("\tNULL,\n"); - } else { - printf("\t\""); - - unsigned int si; - for (si = 0; si < strlen(escapes[i]); si++) { - char ch = escapes[i][si]; - switch (ch) { - case '"': - printf("\\\""); - break; - case '\\': - printf("\\\\"); - break; - default: - printf("%c", escapes[i][si]); - break; - } - } - - printf("\",\n"); - } - } - - printf( "};\n" - "#endif // BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H\n"); -} - -int main (int argc, char *argv[]) -{ - initJsonEscape(); - outputEscape(); - return 0; -} - diff --git a/src/univalue/univalue.cpp b/src/univalue/univalue.cpp deleted file mode 100644 index 6920c44c..00000000 --- a/src/univalue/univalue.cpp +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright 2014 BitPay Inc. -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#include -#include -#include -#include -#include // std::runtime_error - -#include "univalue.h" - -#include "utilstrencodings.h" // ParseXX - -using namespace std; - -const UniValue NullUniValue; - -void UniValue::clear() -{ - typ = VNULL; - val.clear(); - keys.clear(); - values.clear(); -} - -bool UniValue::setNull() -{ - clear(); - return true; -} - -bool UniValue::setBool(bool val_) -{ - clear(); - typ = VBOOL; - if (val_) - val = "1"; - return true; -} - -static bool validNumStr(const string& s) -{ - string tokenVal; - unsigned int consumed; - enum jtokentype tt = getJsonToken(tokenVal, consumed, s.c_str()); - return (tt == JTOK_NUMBER); -} - -bool UniValue::setNumStr(const string& val_) -{ - if (!validNumStr(val_)) - return false; - - clear(); - typ = VNUM; - val = val_; - return true; -} - -bool UniValue::setInt(uint64_t val) -{ - string s; - ostringstream oss; - - oss << val; - - return setNumStr(oss.str()); -} - -bool UniValue::setInt(int64_t val) -{ - string s; - ostringstream oss; - - oss << val; - - return setNumStr(oss.str()); -} - -bool UniValue::setFloat(double val) -{ - string s; - ostringstream oss; - - oss << std::setprecision(16) << val; - - bool ret = setNumStr(oss.str()); - typ = VREAL; - return ret; -} - -bool UniValue::setStr(const string& val_) -{ - clear(); - typ = VSTR; - val = val_; - return true; -} - -bool UniValue::setArray() -{ - clear(); - typ = VARR; - return true; -} - -bool UniValue::setObject() -{ - clear(); - typ = VOBJ; - return true; -} - -bool UniValue::push_back(const UniValue& val) -{ - if (typ != VARR) - return false; - - values.push_back(val); - return true; -} - -bool UniValue::push_backV(const std::vector& vec) -{ - if (typ != VARR) - return false; - - values.insert(values.end(), vec.begin(), vec.end()); - - return true; -} - -bool UniValue::pushKV(const std::string& key, const UniValue& val) -{ - if (typ != VOBJ) - return false; - - keys.push_back(key); - values.push_back(val); - return true; -} - -bool UniValue::pushKVs(const UniValue& obj) -{ - if (typ != VOBJ || obj.typ != VOBJ) - return false; - - for (unsigned int i = 0; i < obj.keys.size(); i++) { - keys.push_back(obj.keys[i]); - values.push_back(obj.values[i]); - } - - return true; -} - -int UniValue::findKey(const std::string& key) const -{ - for (unsigned int i = 0; i < keys.size(); i++) { - if (keys[i] == key) - return (int) i; - } - - return -1; -} - -bool UniValue::checkObject(const std::map& t) -{ - for (std::map::const_iterator it = t.begin(); - it != t.end(); it++) { - int idx = findKey(it->first); - if (idx < 0) - return false; - - if (values[idx].getType() != it->second) - return false; - } - - return true; -} - -const UniValue& UniValue::operator[](const std::string& key) const -{ - if (typ != VOBJ) - return NullUniValue; - - int index = findKey(key); - if (index < 0) - return NullUniValue; - - return values[index]; -} - -const UniValue& UniValue::operator[](unsigned int index) const -{ - if (typ != VOBJ && typ != VARR) - return NullUniValue; - if (index >= values.size()) - return NullUniValue; - - return values[index]; -} - -const char *uvTypeName(UniValue::VType t) -{ - switch (t) { - case UniValue::VNULL: return "null"; - case UniValue::VBOOL: return "bool"; - case UniValue::VOBJ: return "object"; - case UniValue::VARR: return "array"; - case UniValue::VSTR: return "string"; - case UniValue::VNUM: return "number"; - case UniValue::VREAL: return "number"; - } - - // not reached - return NULL; -} - -const UniValue& find_value( const UniValue& obj, const std::string& name) -{ - for (unsigned int i = 0; i < obj.keys.size(); i++) - { - if( obj.keys[i] == name ) - { - return obj.values[i]; - } - } - - return NullUniValue; -} - -std::vector UniValue::getKeys() const -{ - if (typ != VOBJ) - throw std::runtime_error("JSON value is not an object as expected"); - return keys; -} - -std::vector UniValue::getValues() const -{ - if (typ != VOBJ && typ != VARR) - throw std::runtime_error("JSON value is not an object or array as expected"); - return values; -} - -bool UniValue::get_bool() const -{ - if (typ != VBOOL) - throw std::runtime_error("JSON value is not a boolean as expected"); - return getBool(); -} - -std::string UniValue::get_str() const -{ - if (typ != VSTR) - throw std::runtime_error("JSON value is not a string as expected"); - return getValStr(); -} - -int UniValue::get_int() const -{ - if (typ != VNUM) - throw std::runtime_error("JSON value is not an integer as expected"); - int32_t retval; - if (!ParseInt32(getValStr(), &retval)) - throw std::runtime_error("JSON integer out of range"); - return retval; -} - -int64_t UniValue::get_int64() const -{ - if (typ != VNUM) - throw std::runtime_error("JSON value is not an integer as expected"); - int64_t retval; - if (!ParseInt64(getValStr(), &retval)) - throw std::runtime_error("JSON integer out of range"); - return retval; -} - -double UniValue::get_real() const -{ - if (typ != VREAL && typ != VNUM) - throw std::runtime_error("JSON value is not a number as expected"); - double retval; - if (!ParseDouble(getValStr(), &retval)) - throw std::runtime_error("JSON double out of range"); - return retval; -} - -const UniValue& UniValue::get_obj() const -{ - if (typ != VOBJ) - throw std::runtime_error("JSON value is not an object as expected"); - return *this; -} - -const UniValue& UniValue::get_array() const -{ - if (typ != VARR) - throw std::runtime_error("JSON value is not an array as expected"); - return *this; -} - diff --git a/src/univalue/univalue.h b/src/univalue/univalue.h deleted file mode 100644 index 79018bb7..00000000 --- a/src/univalue/univalue.h +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright 2014 BitPay Inc. -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef BITCOIN_UNIVALUE_UNIVALUE_H -#define BITCOIN_UNIVALUE_UNIVALUE_H - -#include -#include -#include -#include -#include - -#include // .get_int64() -#include // std::pair - -class UniValue { -public: - enum VType { VNULL, VOBJ, VARR, VSTR, VNUM, VREAL, VBOOL, }; - - UniValue() { typ = VNULL; } - UniValue(UniValue::VType initialType, const std::string& initialStr = "") { - typ = initialType; - val = initialStr; - } - UniValue(uint64_t val_) { - setInt(val_); - } - UniValue(int64_t val_) { - setInt(val_); - } - UniValue(bool val_) { - setBool(val_); - } - UniValue(int val_) { - setInt(val_); - } - UniValue(double val_) { - setFloat(val_); - } - UniValue(const std::string& val_) { - setStr(val_); - } - UniValue(const char *val_) { - std::string s(val_); - setStr(s); - } - ~UniValue() {} - - void clear(); - - bool setNull(); - bool setBool(bool val); - bool setNumStr(const std::string& val); - bool setInt(uint64_t val); - bool setInt(int64_t val); - bool setInt(int val) { return setInt((int64_t)val); } - bool setFloat(double val); - bool setStr(const std::string& val); - bool setArray(); - bool setObject(); - - enum VType getType() const { return typ; } - std::string getValStr() const { return val; } - bool empty() const { return (values.size() == 0); } - - size_t size() const { return values.size(); } - - bool getBool() const { return isTrue(); } - bool checkObject(const std::map& memberTypes); - const UniValue& operator[](const std::string& key) const; - const UniValue& operator[](unsigned int index) const; - bool exists(const std::string& key) const { return (findKey(key) >= 0); } - - bool isNull() const { return (typ == VNULL); } - bool isTrue() const { return (typ == VBOOL) && (val == "1"); } - bool isFalse() const { return (typ == VBOOL) && (val != "1"); } - bool isBool() const { return (typ == VBOOL); } - bool isStr() const { return (typ == VSTR); } - bool isNum() const { return (typ == VNUM); } - bool isReal() const { return (typ == VREAL); } - bool isArray() const { return (typ == VARR); } - bool isObject() const { return (typ == VOBJ); } - - bool push_back(const UniValue& val); - bool push_back(const std::string& val_) { - UniValue tmpVal(VSTR, val_); - return push_back(tmpVal); - } - bool push_back(const char *val_) { - std::string s(val_); - return push_back(s); - } - bool push_backV(const std::vector& vec); - - bool pushKV(const std::string& key, const UniValue& val); - bool pushKV(const std::string& key, const std::string& val) { - UniValue tmpVal(VSTR, val); - return pushKV(key, tmpVal); - } - bool pushKV(const std::string& key, const char *val_) { - std::string val(val_); - return pushKV(key, val); - } - bool pushKV(const std::string& key, int64_t val) { - UniValue tmpVal(val); - return pushKV(key, tmpVal); - } - bool pushKV(const std::string& key, uint64_t val) { - UniValue tmpVal(val); - return pushKV(key, tmpVal); - } - bool pushKV(const std::string& key, int val) { - UniValue tmpVal((int64_t)val); - return pushKV(key, tmpVal); - } - bool pushKV(const std::string& key, double val) { - UniValue tmpVal(val); - return pushKV(key, tmpVal); - } - bool pushKVs(const UniValue& obj); - - std::string write(unsigned int prettyIndent = 0, - unsigned int indentLevel = 0) const; - - bool read(const char *raw); - bool read(const std::string& rawStr) { - return read(rawStr.c_str()); - } - -private: - UniValue::VType typ; - std::string val; // numbers are stored as C++ strings - std::vector keys; - std::vector values; - - int findKey(const std::string& key) const; - void writeArray(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const; - void writeObject(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const; - -public: - // Strict type-specific getters, these throw std::runtime_error if the - // value is of unexpected type - std::vector getKeys() const; - std::vector getValues() const; - bool get_bool() const; - std::string get_str() const; - int get_int() const; - int64_t get_int64() const; - double get_real() const; - const UniValue& get_obj() const; - const UniValue& get_array() const; - - enum VType type() const { return getType(); } - bool push_back(std::pair pear) { - return pushKV(pear.first, pear.second); - } - friend const UniValue& find_value( const UniValue& obj, const std::string& name); -}; - -// -// The following were added for compatibility with json_spirit. -// Most duplicate other methods, and should be removed. -// -static inline std::pair Pair(const char *cKey, const char *cVal) -{ - std::string key(cKey); - UniValue uVal(cVal); - return std::make_pair(key, uVal); -} - -static inline std::pair Pair(const char *cKey, std::string strVal) -{ - std::string key(cKey); - UniValue uVal(strVal); - return std::make_pair(key, uVal); -} - -static inline std::pair Pair(const char *cKey, uint64_t u64Val) -{ - std::string key(cKey); - UniValue uVal(u64Val); - return std::make_pair(key, uVal); -} - -static inline std::pair Pair(const char *cKey, int64_t i64Val) -{ - std::string key(cKey); - UniValue uVal(i64Val); - return std::make_pair(key, uVal); -} - -static inline std::pair Pair(const char *cKey, bool iVal) -{ - std::string key(cKey); - UniValue uVal(iVal); - return std::make_pair(key, uVal); -} - -static inline std::pair Pair(const char *cKey, int iVal) -{ - std::string key(cKey); - UniValue uVal(iVal); - return std::make_pair(key, uVal); -} - -static inline std::pair Pair(const char *cKey, double dVal) -{ - std::string key(cKey); - UniValue uVal(dVal); - return std::make_pair(key, uVal); -} - -static inline std::pair Pair(const char *cKey, const UniValue& uVal) -{ - std::string key(cKey); - return std::make_pair(key, uVal); -} - -static inline std::pair Pair(std::string key, const UniValue& uVal) -{ - return std::make_pair(key, uVal); -} - -enum jtokentype { - JTOK_ERR = -1, - JTOK_NONE = 0, // eof - JTOK_OBJ_OPEN, - JTOK_OBJ_CLOSE, - JTOK_ARR_OPEN, - JTOK_ARR_CLOSE, - JTOK_COLON, - JTOK_COMMA, - JTOK_KW_NULL, - JTOK_KW_TRUE, - JTOK_KW_FALSE, - JTOK_NUMBER, - JTOK_STRING, -}; - -extern enum jtokentype getJsonToken(std::string& tokenVal, - unsigned int& consumed, const char *raw); -extern const char *uvTypeName(UniValue::VType t); - -extern const UniValue NullUniValue; - -const UniValue& find_value( const UniValue& obj, const std::string& name); - -#endif // BITCOIN_UNIVALUE_UNIVALUE_H - diff --git a/src/univalue/univalue_escapes.h b/src/univalue/univalue_escapes.h deleted file mode 100644 index 05141182..00000000 --- a/src/univalue/univalue_escapes.h +++ /dev/null @@ -1,262 +0,0 @@ -// Automatically generated file. Do not modify. -#ifndef BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H -#define BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H -static const char *escapes[256] = { - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - "\\b", - "\\t", - "\\n", - NULL, - "\\f", - "\\r", - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - "\\\"", - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - "\\/", - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - "\\\\", - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, -}; -#endif // BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H diff --git a/src/univalue/univalue_read.cpp b/src/univalue/univalue_read.cpp deleted file mode 100644 index 5cea7789..00000000 --- a/src/univalue/univalue_read.cpp +++ /dev/null @@ -1,390 +0,0 @@ -// Copyright 2014 BitPay Inc. -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#include -#include -#include -#include "univalue.h" - -using namespace std; - -// convert hexadecimal string to unsigned integer -static const char *hatoui(const char *first, const char *last, - unsigned int& out) -{ - unsigned int result = 0; - for (; first != last; ++first) - { - int digit; - if (isdigit(*first)) - digit = *first - '0'; - - else if (*first >= 'a' && *first <= 'f') - digit = *first - 'a' + 10; - - else if (*first >= 'A' && *first <= 'F') - digit = *first - 'A' + 10; - - else - break; - - result = 16 * result + digit; - } - out = result; - - return first; -} - -enum jtokentype getJsonToken(string& tokenVal, unsigned int& consumed, - const char *raw) -{ - tokenVal.clear(); - consumed = 0; - - const char *rawStart = raw; - - while ((*raw) && (isspace(*raw))) // skip whitespace - raw++; - - switch (*raw) { - - case 0: - return JTOK_NONE; - - case '{': - raw++; - consumed = (raw - rawStart); - return JTOK_OBJ_OPEN; - case '}': - raw++; - consumed = (raw - rawStart); - return JTOK_OBJ_CLOSE; - case '[': - raw++; - consumed = (raw - rawStart); - return JTOK_ARR_OPEN; - case ']': - raw++; - consumed = (raw - rawStart); - return JTOK_ARR_CLOSE; - - case ':': - raw++; - consumed = (raw - rawStart); - return JTOK_COLON; - case ',': - raw++; - consumed = (raw - rawStart); - return JTOK_COMMA; - - case 'n': - case 't': - case 'f': - if (!strncmp(raw, "null", 4)) { - raw += 4; - consumed = (raw - rawStart); - return JTOK_KW_NULL; - } else if (!strncmp(raw, "true", 4)) { - raw += 4; - consumed = (raw - rawStart); - return JTOK_KW_TRUE; - } else if (!strncmp(raw, "false", 5)) { - raw += 5; - consumed = (raw - rawStart); - return JTOK_KW_FALSE; - } else - return JTOK_ERR; - - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': { - // part 1: int - string numStr; - - const char *first = raw; - - const char *firstDigit = first; - if (!isdigit(*firstDigit)) - firstDigit++; - if ((*firstDigit == '0') && isdigit(firstDigit[1])) - return JTOK_ERR; - - numStr += *raw; // copy first char - raw++; - - if ((*first == '-') && (!isdigit(*raw))) - return JTOK_ERR; - - while ((*raw) && isdigit(*raw)) { // copy digits - numStr += *raw; - raw++; - } - - // part 2: frac - if (*raw == '.') { - numStr += *raw; // copy . - raw++; - - if (!isdigit(*raw)) - return JTOK_ERR; - while ((*raw) && isdigit(*raw)) { // copy digits - numStr += *raw; - raw++; - } - } - - // part 3: exp - if (*raw == 'e' || *raw == 'E') { - numStr += *raw; // copy E - raw++; - - if (*raw == '-' || *raw == '+') { // copy +/- - numStr += *raw; - raw++; - } - - if (!isdigit(*raw)) - return JTOK_ERR; - while ((*raw) && isdigit(*raw)) { // copy digits - numStr += *raw; - raw++; - } - } - - tokenVal = numStr; - consumed = (raw - rawStart); - return JTOK_NUMBER; - } - - case '"': { - raw++; // skip " - - string valStr; - - while (*raw) { - if (*raw < 0x20) - return JTOK_ERR; - - else if (*raw == '\\') { - raw++; // skip backslash - - switch (*raw) { - case '"': valStr += "\""; break; - case '\\': valStr += "\\"; break; - case '/': valStr += "/"; break; - case 'b': valStr += "\b"; break; - case 'f': valStr += "\f"; break; - case 'n': valStr += "\n"; break; - case 'r': valStr += "\r"; break; - case 't': valStr += "\t"; break; - - case 'u': { - char buf[4] = {0,0,0,0}; - char *last = &buf[0]; - unsigned int codepoint; - if (hatoui(raw + 1, raw + 1 + 4, codepoint) != - raw + 1 + 4) - return JTOK_ERR; - - if (codepoint <= 0x7f) - *last = (char)codepoint; - else if (codepoint <= 0x7FF) { - *last++ = (char)(0xC0 | (codepoint >> 6)); - *last = (char)(0x80 | (codepoint & 0x3F)); - } else if (codepoint <= 0xFFFF) { - *last++ = (char)(0xE0 | (codepoint >> 12)); - *last++ = (char)(0x80 | ((codepoint >> 6) & 0x3F)); - *last = (char)(0x80 | (codepoint & 0x3F)); - } - - valStr += buf; - raw += 4; - break; - } - default: - return JTOK_ERR; - - } - - raw++; // skip esc'd char - } - - else if (*raw == '"') { - raw++; // skip " - break; // stop scanning - } - - else { - valStr += *raw; - raw++; - } - } - - tokenVal = valStr; - consumed = (raw - rawStart); - return JTOK_STRING; - } - - default: - return JTOK_ERR; - } -} - -bool UniValue::read(const char *raw) -{ - clear(); - - bool expectName = false; - bool expectColon = false; - vector stack; - - enum jtokentype tok = JTOK_NONE; - enum jtokentype last_tok = JTOK_NONE; - while (1) { - last_tok = tok; - - string tokenVal; - unsigned int consumed; - tok = getJsonToken(tokenVal, consumed, raw); - if (tok == JTOK_NONE || tok == JTOK_ERR) - break; - raw += consumed; - - switch (tok) { - - case JTOK_OBJ_OPEN: - case JTOK_ARR_OPEN: { - VType utyp = (tok == JTOK_OBJ_OPEN ? VOBJ : VARR); - if (!stack.size()) { - if (utyp == VOBJ) - setObject(); - else - setArray(); - stack.push_back(this); - } else { - UniValue tmpVal(utyp); - UniValue *top = stack.back(); - top->values.push_back(tmpVal); - - UniValue *newTop = &(top->values.back()); - stack.push_back(newTop); - } - - if (utyp == VOBJ) - expectName = true; - break; - } - - case JTOK_OBJ_CLOSE: - case JTOK_ARR_CLOSE: { - if (!stack.size() || expectColon || (last_tok == JTOK_COMMA)) - return false; - - VType utyp = (tok == JTOK_OBJ_CLOSE ? VOBJ : VARR); - UniValue *top = stack.back(); - if (utyp != top->getType()) - return false; - - stack.pop_back(); - expectName = false; - break; - } - - case JTOK_COLON: { - if (!stack.size() || expectName || !expectColon) - return false; - - UniValue *top = stack.back(); - if (top->getType() != VOBJ) - return false; - - expectColon = false; - break; - } - - case JTOK_COMMA: { - if (!stack.size() || expectName || expectColon || - (last_tok == JTOK_COMMA) || (last_tok == JTOK_ARR_OPEN)) - return false; - - UniValue *top = stack.back(); - if (top->getType() == VOBJ) - expectName = true; - break; - } - - case JTOK_KW_NULL: - case JTOK_KW_TRUE: - case JTOK_KW_FALSE: { - if (!stack.size() || expectName || expectColon) - return false; - - UniValue tmpVal; - switch (tok) { - case JTOK_KW_NULL: - // do nothing more - break; - case JTOK_KW_TRUE: - tmpVal.setBool(true); - break; - case JTOK_KW_FALSE: - tmpVal.setBool(false); - break; - default: /* impossible */ break; - } - - UniValue *top = stack.back(); - top->values.push_back(tmpVal); - - break; - } - - case JTOK_NUMBER: { - if (!stack.size() || expectName || expectColon) - return false; - - UniValue tmpVal(VNUM, tokenVal); - UniValue *top = stack.back(); - top->values.push_back(tmpVal); - - break; - } - - case JTOK_STRING: { - if (!stack.size()) - return false; - - UniValue *top = stack.back(); - - if (expectName) { - top->keys.push_back(tokenVal); - expectName = false; - expectColon = true; - } else { - UniValue tmpVal(VSTR, tokenVal); - top->values.push_back(tmpVal); - } - - break; - } - - default: - return false; - } - } - - if (stack.size() != 0) - return false; - - return true; -} - diff --git a/src/univalue/univalue_write.cpp b/src/univalue/univalue_write.cpp deleted file mode 100644 index d360c253..00000000 --- a/src/univalue/univalue_write.cpp +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2014 BitPay Inc. -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#include -#include -#include -#include -#include "univalue.h" -#include "univalue_escapes.h" - -// TODO: Using UTF8 - -using namespace std; - -static string json_escape(const string& inS) -{ - string outS; - outS.reserve(inS.size() * 2); - - for (unsigned int i = 0; i < inS.size(); i++) { - unsigned char ch = inS[i]; - const char *escStr = escapes[ch]; - - if (escStr) - outS += escStr; - - else if (isprint(ch)) - outS += ch; - - else { - char tmpesc[16]; - sprintf(tmpesc, "\\u%04x", ch); - outS += tmpesc; - } - } - - return outS; -} - -string UniValue::write(unsigned int prettyIndent, - unsigned int indentLevel) const -{ - string s; - s.reserve(1024); - - unsigned int modIndent = indentLevel; - if (modIndent == 0) - modIndent = 1; - - switch (typ) { - case VNULL: - s += "null"; - break; - case VOBJ: - writeObject(prettyIndent, modIndent, s); - break; - case VARR: - writeArray(prettyIndent, modIndent, s); - break; - case VSTR: - s += "\"" + json_escape(val) + "\""; - break; - case VREAL: - { - std::stringstream ss; - ss << std::showpoint << std::fixed << std::setprecision(8) << get_real(); - s += ss.str(); - } - break; - case VNUM: - s += val; - break; - case VBOOL: - s += (val == "1" ? "true" : "false"); - break; - } - - return s; -} - -static void indentStr(unsigned int prettyIndent, unsigned int indentLevel, string& s) -{ - s.append(prettyIndent * indentLevel, ' '); -} - -void UniValue::writeArray(unsigned int prettyIndent, unsigned int indentLevel, string& s) const -{ - s += "["; - if (prettyIndent) - s += "\n"; - - for (unsigned int i = 0; i < values.size(); i++) { - if (prettyIndent) - indentStr(prettyIndent, indentLevel, s); - s += values[i].write(prettyIndent, indentLevel + 1); - if (i != (values.size() - 1)) { - s += ","; - if (prettyIndent) - s += " "; - } - if (prettyIndent) - s += "\n"; - } - - if (prettyIndent) - indentStr(prettyIndent, indentLevel - 1, s); - s += "]"; -} - -void UniValue::writeObject(unsigned int prettyIndent, unsigned int indentLevel, string& s) const -{ - s += "{"; - if (prettyIndent) - s += "\n"; - - for (unsigned int i = 0; i < keys.size(); i++) { - if (prettyIndent) - indentStr(prettyIndent, indentLevel, s); - s += "\"" + json_escape(keys[i]) + "\":"; - if (prettyIndent) - s += " "; - s += values[i].write(prettyIndent, indentLevel + 1); - if (i != (values.size() - 1)) - s += ","; - if (prettyIndent) - s += "\n"; - } - - if (prettyIndent) - indentStr(prettyIndent, indentLevel - 1, s); - s += "}"; -} - From a4071034f6ad640ef91057fa3f45098c4933f444 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 10 Feb 2017 02:18:44 +0000 Subject: [PATCH 02/16] Squashed 'src/univalue/' content from commit 9ef5b78 git-subtree-dir: src/univalue git-subtree-split: 9ef5b78c1998509b8f1ccd76f0aee15140e384be --- .gitignore | 32 +++ .travis.yml | 52 ++++ COPYING | 19 ++ Makefile.am | 109 ++++++++ README | 7 + TODO | 10 + autogen.sh | 9 + build-aux/m4/.gitignore | 1 + configure.ac | 69 +++++ gen/gen.cpp | 84 ++++++ include/univalue.h | 296 ++++++++++++++++++++ lib/.gitignore | 2 + lib/univalue.cpp | 359 ++++++++++++++++++++++++ lib/univalue_escapes.h | 262 ++++++++++++++++++ lib/univalue_read.cpp | 454 +++++++++++++++++++++++++++++++ lib/univalue_utffilter.h | 119 ++++++++ lib/univalue_write.cpp | 115 ++++++++ pc/libunivalue-uninstalled.pc.in | 9 + pc/libunivalue.pc.in | 10 + test/.gitignore | 6 + test/fail1.json | 1 + test/fail10.json | 1 + test/fail11.json | 1 + test/fail12.json | 1 + test/fail13.json | 1 + test/fail14.json | 1 + test/fail15.json | 1 + test/fail16.json | 1 + test/fail17.json | 1 + test/fail18.json | 1 + test/fail19.json | 1 + test/fail2.json | 1 + test/fail20.json | 1 + test/fail21.json | 1 + test/fail22.json | 1 + test/fail23.json | 1 + test/fail24.json | 1 + test/fail25.json | 1 + test/fail26.json | 1 + test/fail27.json | 2 + test/fail28.json | 2 + test/fail29.json | 1 + test/fail3.json | 1 + test/fail30.json | 1 + test/fail31.json | 1 + test/fail32.json | 1 + test/fail33.json | 1 + test/fail34.json | 1 + test/fail35.json | 1 + test/fail36.json | 1 + test/fail37.json | 1 + test/fail38.json | 1 + test/fail39.json | 1 + test/fail4.json | 1 + test/fail40.json | 1 + test/fail41.json | 1 + test/fail42.json | Bin 0 -> 37 bytes test/fail5.json | 1 + test/fail6.json | 1 + test/fail7.json | 1 + test/fail8.json | 1 + test/fail9.json | 1 + test/no_nul.cpp | 8 + test/pass1.json | 58 ++++ test/pass2.json | 1 + test/pass3.json | 6 + test/round1.json | 1 + test/round2.json | 1 + test/round3.json | 1 + test/round4.json | 1 + test/round5.json | 1 + test/round6.json | 1 + test/round7.json | 1 + test/test_json.cpp | 24 ++ test/unitester.cpp | 169 ++++++++++++ 75 files changed, 2340 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 COPYING create mode 100644 Makefile.am create mode 100644 README create mode 100644 TODO create mode 100755 autogen.sh create mode 100644 build-aux/m4/.gitignore create mode 100644 configure.ac create mode 100644 gen/gen.cpp create mode 100644 include/univalue.h create mode 100644 lib/.gitignore create mode 100644 lib/univalue.cpp create mode 100644 lib/univalue_escapes.h create mode 100644 lib/univalue_read.cpp create mode 100644 lib/univalue_utffilter.h create mode 100644 lib/univalue_write.cpp create mode 100644 pc/libunivalue-uninstalled.pc.in create mode 100644 pc/libunivalue.pc.in create mode 100644 test/.gitignore create mode 100644 test/fail1.json create mode 100644 test/fail10.json create mode 100644 test/fail11.json create mode 100644 test/fail12.json create mode 100644 test/fail13.json create mode 100644 test/fail14.json create mode 100644 test/fail15.json create mode 100644 test/fail16.json create mode 100644 test/fail17.json create mode 100644 test/fail18.json create mode 100644 test/fail19.json create mode 100644 test/fail2.json create mode 100644 test/fail20.json create mode 100644 test/fail21.json create mode 100644 test/fail22.json create mode 100644 test/fail23.json create mode 100644 test/fail24.json create mode 100644 test/fail25.json create mode 100644 test/fail26.json create mode 100644 test/fail27.json create mode 100644 test/fail28.json create mode 100644 test/fail29.json create mode 100644 test/fail3.json create mode 100644 test/fail30.json create mode 100644 test/fail31.json create mode 100644 test/fail32.json create mode 100644 test/fail33.json create mode 100644 test/fail34.json create mode 100644 test/fail35.json create mode 100644 test/fail36.json create mode 100644 test/fail37.json create mode 100644 test/fail38.json create mode 100644 test/fail39.json create mode 100644 test/fail4.json create mode 100644 test/fail40.json create mode 100644 test/fail41.json create mode 100644 test/fail42.json create mode 100644 test/fail5.json create mode 100644 test/fail6.json create mode 100644 test/fail7.json create mode 100644 test/fail8.json create mode 100644 test/fail9.json create mode 100644 test/no_nul.cpp create mode 100644 test/pass1.json create mode 100644 test/pass2.json create mode 100644 test/pass3.json create mode 100644 test/round1.json create mode 100644 test/round2.json create mode 100644 test/round3.json create mode 100644 test/round4.json create mode 100644 test/round5.json create mode 100644 test/round6.json create mode 100644 test/round7.json create mode 100644 test/test_json.cpp create mode 100644 test/unitester.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..19e42f81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +.deps/ +INSTALL +Makefile +Makefile.in +aclocal.m4 +autom4te.cache/ +compile +config.log +config.status +config.guess +config.sub +configure +depcomp +install-sh +missing +stamp-h1 +univalue-config.h* +test-driver +libtool +ltmain.sh +test-suite.log + +*.a +*.la +*.lo +*.logs +*.o +*.pc +*.trs + +.dirstamp +.libs diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..132743d3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,52 @@ +language: cpp + +compiler: + - clang + - gcc + +os: + - linux + - osx + +sudo: false + +env: + global: + - MAKEJOBS=-j3 + - RUN_TESTS=true + - BASE_OUTDIR=$TRAVIS_BUILD_DIR/out + +cache: + apt: true + +addons: + apt: + packages: + - pkg-config + +before_script: + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew uninstall libtool; brew install libtool; fi + - if [ -n "$USE_SHELL" ]; then export CONFIG_SHELL="$USE_SHELL"; fi + - test -n "$USE_SHELL" && eval '"$USE_SHELL" -c "./autogen.sh"' || ./autogen.sh + +script: + - if [ -n "$UNIVALUE_CONFIG" ]; then unset CC; unset CXX; fi + - OUTDIR=$BASE_OUTDIR/$TRAVIS_PULL_REQUEST/$TRAVIS_JOB_NUMBER-$HOST + - UNIVALUE_CONFIG_ALL="--prefix=$TRAVIS_BUILD_DIR/depends/$HOST --bindir=$OUTDIR/bin --libdir=$OUTDIR/lib" + - ./configure --cache-file=config.cache $UNIVALUE_CONFIG_ALL $UNIVALUE_CONFIG || ( cat config.log && false) + - make -s $MAKEJOBS $GOAL || ( echo "Build failure. Verbose build follows." && make $GOAL ; false ) + - export LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/depends/$HOST/lib + - if [ "$RUN_TESTS" = "true" ]; then make $MAKEJOBS distcheck; fi + +matrix: + fast_finish: true + include: + - os: linux + compiler: gcc + env: UNIVALUE_CONFIG=--host=x86_64-w64-mingw32 RUN_TESTS=false + addons: + apt: + packages: + - g++-mingw-w64-x86-64 + - gcc-mingw-w64-x86-64 + - binutils-mingw-w64-x86-64 diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..1fb429f3 --- /dev/null +++ b/COPYING @@ -0,0 +1,19 @@ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..532fa194 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,109 @@ +ACLOCAL_AMFLAGS = -I build-aux/m4 +.PHONY: gen +.INTERMEDIATE: $(GENBIN) + +include_HEADERS = include/univalue.h +noinst_HEADERS = lib/univalue_escapes.h lib/univalue_utffilter.h + +lib_LTLIBRARIES = libunivalue.la + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = pc/libunivalue.pc + +libunivalue_la_SOURCES = \ + lib/univalue.cpp \ + lib/univalue_read.cpp \ + lib/univalue_write.cpp + +libunivalue_la_LDFLAGS = \ + -version-info $(LIBUNIVALUE_CURRENT):$(LIBUNIVALUE_REVISION):$(LIBUNIVALUE_AGE) \ + -no-undefined +libunivalue_la_CXXFLAGS = -I$(top_srcdir)/include + +TESTS = test/unitester test/no_nul + +GENBIN = gen/gen$(BUILD_EXEEXT) +GEN_SRCS = gen/gen.cpp + +$(GENBIN): $(GEN_SRCS) + @echo Building $@ + $(AM_V_at)c++ -I$(top_srcdir)/include -o $@ $< + +gen: lib/univalue_escapes.h $(GENBIN) + @echo Updating $< + $(AM_V_at)$(GENBIN) > lib/univalue_escapes.h + +noinst_PROGRAMS = $(TESTS) test/test_json + +TEST_DATA_DIR=test + +test_unitester_SOURCES = test/unitester.cpp +test_unitester_LDADD = libunivalue.la +test_unitester_CXXFLAGS = -I$(top_srcdir)/include -DJSON_TEST_SRC=\"$(srcdir)/$(TEST_DATA_DIR)\" +test_unitester_LDFLAGS = -static $(LIBTOOL_APP_LDFLAGS) + +test_test_json_SOURCES = test/test_json.cpp +test_test_json_LDADD = libunivalue.la +test_test_json_CXXFLAGS = -I$(top_srcdir)/include +test_test_json_LDFLAGS = -static $(LIBTOOL_APP_LDFLAGS) + +test_no_nul_SOURCES = test/no_nul.cpp +test_no_nul_LDADD = libunivalue.la +test_no_nul_CXXFLAGS = -I$(top_srcdir)/include +test_no_nul_LDFLAGS = -static $(LIBTOOL_APP_LDFLAGS) + +TEST_FILES = \ + $(TEST_DATA_DIR)/fail10.json \ + $(TEST_DATA_DIR)/fail11.json \ + $(TEST_DATA_DIR)/fail12.json \ + $(TEST_DATA_DIR)/fail13.json \ + $(TEST_DATA_DIR)/fail14.json \ + $(TEST_DATA_DIR)/fail15.json \ + $(TEST_DATA_DIR)/fail16.json \ + $(TEST_DATA_DIR)/fail17.json \ + $(TEST_DATA_DIR)/fail18.json \ + $(TEST_DATA_DIR)/fail19.json \ + $(TEST_DATA_DIR)/fail1.json \ + $(TEST_DATA_DIR)/fail20.json \ + $(TEST_DATA_DIR)/fail21.json \ + $(TEST_DATA_DIR)/fail22.json \ + $(TEST_DATA_DIR)/fail23.json \ + $(TEST_DATA_DIR)/fail24.json \ + $(TEST_DATA_DIR)/fail25.json \ + $(TEST_DATA_DIR)/fail26.json \ + $(TEST_DATA_DIR)/fail27.json \ + $(TEST_DATA_DIR)/fail28.json \ + $(TEST_DATA_DIR)/fail29.json \ + $(TEST_DATA_DIR)/fail2.json \ + $(TEST_DATA_DIR)/fail30.json \ + $(TEST_DATA_DIR)/fail31.json \ + $(TEST_DATA_DIR)/fail32.json \ + $(TEST_DATA_DIR)/fail33.json \ + $(TEST_DATA_DIR)/fail34.json \ + $(TEST_DATA_DIR)/fail35.json \ + $(TEST_DATA_DIR)/fail36.json \ + $(TEST_DATA_DIR)/fail37.json \ + $(TEST_DATA_DIR)/fail38.json \ + $(TEST_DATA_DIR)/fail39.json \ + $(TEST_DATA_DIR)/fail40.json \ + $(TEST_DATA_DIR)/fail41.json \ + $(TEST_DATA_DIR)/fail42.json \ + $(TEST_DATA_DIR)/fail3.json \ + $(TEST_DATA_DIR)/fail4.json \ + $(TEST_DATA_DIR)/fail5.json \ + $(TEST_DATA_DIR)/fail6.json \ + $(TEST_DATA_DIR)/fail7.json \ + $(TEST_DATA_DIR)/fail8.json \ + $(TEST_DATA_DIR)/fail9.json \ + $(TEST_DATA_DIR)/pass1.json \ + $(TEST_DATA_DIR)/pass2.json \ + $(TEST_DATA_DIR)/pass3.json \ + $(TEST_DATA_DIR)/round1.json \ + $(TEST_DATA_DIR)/round2.json \ + $(TEST_DATA_DIR)/round3.json \ + $(TEST_DATA_DIR)/round4.json \ + $(TEST_DATA_DIR)/round5.json \ + $(TEST_DATA_DIR)/round6.json \ + $(TEST_DATA_DIR)/round7.json + +EXTRA_DIST=$(TEST_FILES) $(GEN_SRCS) diff --git a/README b/README new file mode 100644 index 00000000..48167b08 --- /dev/null +++ b/README @@ -0,0 +1,7 @@ + + UniValue + +A universal value object, with JSON encoding (output) and decoding (input). + +Built as a single dynamic RAII C++ object class, and no templates. + diff --git a/TODO b/TODO new file mode 100644 index 00000000..5530048e --- /dev/null +++ b/TODO @@ -0,0 +1,10 @@ + +Rearrange tree for easier 'git subtree' style use + +Move towards C++11 etc. + +Namespace support - must come up with useful shorthand, avoiding +long Univalue::Univalue::Univalue usages forced upon library users. + +Improve test suite + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 00000000..4b38721f --- /dev/null +++ b/autogen.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -e +srcdir="$(dirname $0)" +cd "$srcdir" +if [ -z ${LIBTOOLIZE} ] && GLIBTOOLIZE="`which glibtoolize 2>/dev/null`"; then + LIBTOOLIZE="${GLIBTOOLIZE}" + export LIBTOOLIZE +fi +autoreconf --install --force diff --git a/build-aux/m4/.gitignore b/build-aux/m4/.gitignore new file mode 100644 index 00000000..f0636865 --- /dev/null +++ b/build-aux/m4/.gitignore @@ -0,0 +1 @@ +/*.m4 diff --git a/configure.ac b/configure.ac new file mode 100644 index 00000000..8298332a --- /dev/null +++ b/configure.ac @@ -0,0 +1,69 @@ +m4_define([libunivalue_major_version], [1]) +m4_define([libunivalue_minor_version], [1]) +m4_define([libunivalue_micro_version], [3]) +m4_define([libunivalue_interface_age], [3]) +# If you need a modifier for the version number. +# Normally empty, but can be used to make "fixup" releases. +m4_define([libunivalue_extraversion], []) + +dnl libtool versioning from libunivalue +m4_define([libunivalue_current], [m4_eval(100 * libunivalue_minor_version + libunivalue_micro_version - libunivalue_interface_age)]) +m4_define([libunivalue_binary_age], [m4_eval(100 * libunivalue_minor_version + libunivalue_micro_version)]) +m4_define([libunivalue_revision], [libunivalue_interface_age]) +m4_define([libunivalue_age], [m4_eval(libunivalue_binary_age - libunivalue_interface_age)]) +m4_define([libunivalue_version], [libunivalue_major_version().libunivalue_minor_version().libunivalue_micro_version()libunivalue_extraversion()]) + + +AC_INIT([univalue], [1.0.3], + [http://github.com/jgarzik/univalue/]) + +dnl make the compilation flags quiet unless V=1 is used +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +AC_PREREQ(2.60) +AC_CONFIG_SRCDIR([lib/univalue.cpp]) +AC_CONFIG_AUX_DIR([build-aux]) +AC_CONFIG_MACRO_DIR([build-aux/m4]) +AC_CONFIG_HEADERS([univalue-config.h]) +AM_INIT_AUTOMAKE([subdir-objects foreign]) + +LIBUNIVALUE_MAJOR_VERSION=libunivalue_major_version +LIBUNIVALUE_MINOR_VERSION=libunivalue_minor_version +LIBUNIVALUE_MICRO_VERSION=libunivalue_micro_version +LIBUNIVALUE_INTERFACE_AGE=libunivalue_interface_age + +# ABI version +# http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html +LIBUNIVALUE_CURRENT=libunivalue_current +LIBUNIVALUE_REVISION=libunivalue_revision +LIBUNIVALUE_AGE=libunivalue_age + +AC_SUBST(LIBUNIVALUE_CURRENT) +AC_SUBST(LIBUNIVALUE_REVISION) +AC_SUBST(LIBUNIVALUE_AGE) + +LT_INIT +LT_LANG([C++]) + +case $host in + *mingw*) + LIBTOOL_APP_LDFLAGS="$LIBTOOL_APP_LDFLAGS -all-static" + ;; +esac + +BUILD_EXEEXT= +case $build in + *mingw*) + BUILD_EXEEXT=".exe" + ;; +esac + +AC_CONFIG_FILES([ + Makefile + pc/libunivalue.pc + pc/libunivalue-uninstalled.pc]) + +AC_SUBST(LIBTOOL_APP_LDFLAGS) +AC_SUBST(BUILD_EXEEXT) +AC_OUTPUT + diff --git a/gen/gen.cpp b/gen/gen.cpp new file mode 100644 index 00000000..17f36194 --- /dev/null +++ b/gen/gen.cpp @@ -0,0 +1,84 @@ +// Copyright 2014 BitPay Inc. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +// +// To re-create univalue_escapes.h: +// $ g++ -o gen gen.cpp +// $ ./gen > univalue_escapes.h +// + +#include +#include +#include "univalue.h" + +using namespace std; + +static bool initEscapes; +static std::string escapes[256]; + +static void initJsonEscape() +{ + // Escape all lower control characters (some get overridden with smaller sequences below) + for (int ch=0x00; ch<0x20; ++ch) { + char tmpbuf[20]; + snprintf(tmpbuf, sizeof(tmpbuf), "\\u%04x", ch); + escapes[ch] = std::string(tmpbuf); + } + + escapes[(int)'"'] = "\\\""; + escapes[(int)'\\'] = "\\\\"; + escapes[(int)'\b'] = "\\b"; + escapes[(int)'\f'] = "\\f"; + escapes[(int)'\n'] = "\\n"; + escapes[(int)'\r'] = "\\r"; + escapes[(int)'\t'] = "\\t"; + escapes[(int)'\x7f'] = "\\u007f"; // U+007F DELETE + + initEscapes = true; +} + +static void outputEscape() +{ + printf( "// Automatically generated file. Do not modify.\n" + "#ifndef BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H\n" + "#define BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H\n" + "static const char *escapes[256] = {\n"); + + for (unsigned int i = 0; i < 256; i++) { + if (escapes[i].empty()) { + printf("\tNULL,\n"); + } else { + printf("\t\""); + + unsigned int si; + for (si = 0; si < escapes[i].size(); si++) { + char ch = escapes[i][si]; + switch (ch) { + case '"': + printf("\\\""); + break; + case '\\': + printf("\\\\"); + break; + default: + printf("%c", escapes[i][si]); + break; + } + } + + printf("\",\n"); + } + } + + printf( "};\n" + "#endif // BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H\n"); +} + +int main (int argc, char *argv[]) +{ + initJsonEscape(); + outputEscape(); + return 0; +} + diff --git a/include/univalue.h b/include/univalue.h new file mode 100644 index 00000000..07314bc5 --- /dev/null +++ b/include/univalue.h @@ -0,0 +1,296 @@ +// Copyright 2014 BitPay Inc. +// Copyright 2015 Bitcoin Core Developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef __UNIVALUE_H__ +#define __UNIVALUE_H__ + +#include + +#include +#include +#include +#include + +#include // .get_int64() +#include // std::pair + +class UniValue { +public: + enum VType { VNULL, VOBJ, VARR, VSTR, VNUM, VBOOL, }; + + UniValue() { typ = VNULL; } + UniValue(UniValue::VType initialType, const std::string& initialStr = "") { + typ = initialType; + val = initialStr; + } + UniValue(uint64_t val_) { + setInt(val_); + } + UniValue(int64_t val_) { + setInt(val_); + } + UniValue(bool val_) { + setBool(val_); + } + UniValue(int val_) { + setInt(val_); + } + UniValue(double val_) { + setFloat(val_); + } + UniValue(const std::string& val_) { + setStr(val_); + } + UniValue(const char *val_) { + std::string s(val_); + setStr(s); + } + ~UniValue() {} + + void clear(); + + bool setNull(); + bool setBool(bool val); + bool setNumStr(const std::string& val); + bool setInt(uint64_t val); + bool setInt(int64_t val); + bool setInt(int val_) { return setInt((int64_t)val_); } + bool setFloat(double val); + bool setStr(const std::string& val); + bool setArray(); + bool setObject(); + + enum VType getType() const { return typ; } + const std::string& getValStr() const { return val; } + bool empty() const { return (values.size() == 0); } + + size_t size() const { return values.size(); } + + bool getBool() const { return isTrue(); } + bool checkObject(const std::map& memberTypes); + const UniValue& operator[](const std::string& key) const; + const UniValue& operator[](size_t index) const; + bool exists(const std::string& key) const { size_t i; return findKey(key, i); } + + bool isNull() const { return (typ == VNULL); } + bool isTrue() const { return (typ == VBOOL) && (val == "1"); } + bool isFalse() const { return (typ == VBOOL) && (val != "1"); } + bool isBool() const { return (typ == VBOOL); } + bool isStr() const { return (typ == VSTR); } + bool isNum() const { return (typ == VNUM); } + bool isArray() const { return (typ == VARR); } + bool isObject() const { return (typ == VOBJ); } + + bool push_back(const UniValue& val); + bool push_back(const std::string& val_) { + UniValue tmpVal(VSTR, val_); + return push_back(tmpVal); + } + bool push_back(const char *val_) { + std::string s(val_); + return push_back(s); + } + bool push_back(uint64_t val_) { + UniValue tmpVal(val_); + return push_back(tmpVal); + } + bool push_back(int64_t val_) { + UniValue tmpVal(val_); + return push_back(tmpVal); + } + bool push_back(int val_) { + UniValue tmpVal(val_); + return push_back(tmpVal); + } + bool push_backV(const std::vector& vec); + + bool pushKV(const std::string& key, const UniValue& val); + bool pushKV(const std::string& key, const std::string& val_) { + UniValue tmpVal(VSTR, val_); + return pushKV(key, tmpVal); + } + bool pushKV(const std::string& key, const char *val_) { + std::string _val(val_); + return pushKV(key, _val); + } + bool pushKV(const std::string& key, int64_t val_) { + UniValue tmpVal(val_); + return pushKV(key, tmpVal); + } + bool pushKV(const std::string& key, uint64_t val_) { + UniValue tmpVal(val_); + return pushKV(key, tmpVal); + } + bool pushKV(const std::string& key, int val_) { + UniValue tmpVal((int64_t)val_); + return pushKV(key, tmpVal); + } + bool pushKV(const std::string& key, double val_) { + UniValue tmpVal(val_); + return pushKV(key, tmpVal); + } + bool pushKVs(const UniValue& obj); + + std::string write(unsigned int prettyIndent = 0, + unsigned int indentLevel = 0) const; + + bool read(const char *raw, size_t len); + bool read(const char *raw); + bool read(const std::string& rawStr) { + return read(rawStr.data(), rawStr.size()); + } + +private: + UniValue::VType typ; + std::string val; // numbers are stored as C++ strings + std::vector keys; + std::vector values; + + bool findKey(const std::string& key, size_t& ret) const; + void writeArray(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const; + void writeObject(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const; + +public: + // Strict type-specific getters, these throw std::runtime_error if the + // value is of unexpected type + const std::vector& getKeys() const; + const std::vector& getValues() const; + bool get_bool() const; + const std::string& get_str() const; + int get_int() const; + int64_t get_int64() const; + double get_real() const; + const UniValue& get_obj() const; + const UniValue& get_array() const; + + enum VType type() const { return getType(); } + bool push_back(std::pair pear) { + return pushKV(pear.first, pear.second); + } + friend const UniValue& find_value( const UniValue& obj, const std::string& name); +}; + +// +// The following were added for compatibility with json_spirit. +// Most duplicate other methods, and should be removed. +// +static inline std::pair Pair(const char *cKey, const char *cVal) +{ + std::string key(cKey); + UniValue uVal(cVal); + return std::make_pair(key, uVal); +} + +static inline std::pair Pair(const char *cKey, std::string strVal) +{ + std::string key(cKey); + UniValue uVal(strVal); + return std::make_pair(key, uVal); +} + +static inline std::pair Pair(const char *cKey, uint64_t u64Val) +{ + std::string key(cKey); + UniValue uVal(u64Val); + return std::make_pair(key, uVal); +} + +static inline std::pair Pair(const char *cKey, int64_t i64Val) +{ + std::string key(cKey); + UniValue uVal(i64Val); + return std::make_pair(key, uVal); +} + +static inline std::pair Pair(const char *cKey, bool iVal) +{ + std::string key(cKey); + UniValue uVal(iVal); + return std::make_pair(key, uVal); +} + +static inline std::pair Pair(const char *cKey, int iVal) +{ + std::string key(cKey); + UniValue uVal(iVal); + return std::make_pair(key, uVal); +} + +static inline std::pair Pair(const char *cKey, double dVal) +{ + std::string key(cKey); + UniValue uVal(dVal); + return std::make_pair(key, uVal); +} + +static inline std::pair Pair(const char *cKey, const UniValue& uVal) +{ + std::string key(cKey); + return std::make_pair(key, uVal); +} + +static inline std::pair Pair(std::string key, const UniValue& uVal) +{ + return std::make_pair(key, uVal); +} + +enum jtokentype { + JTOK_ERR = -1, + JTOK_NONE = 0, // eof + JTOK_OBJ_OPEN, + JTOK_OBJ_CLOSE, + JTOK_ARR_OPEN, + JTOK_ARR_CLOSE, + JTOK_COLON, + JTOK_COMMA, + JTOK_KW_NULL, + JTOK_KW_TRUE, + JTOK_KW_FALSE, + JTOK_NUMBER, + JTOK_STRING, +}; + +extern enum jtokentype getJsonToken(std::string& tokenVal, + unsigned int& consumed, const char *raw, const char *end); +extern const char *uvTypeName(UniValue::VType t); + +static inline bool jsonTokenIsValue(enum jtokentype jtt) +{ + switch (jtt) { + case JTOK_KW_NULL: + case JTOK_KW_TRUE: + case JTOK_KW_FALSE: + case JTOK_NUMBER: + case JTOK_STRING: + return true; + + default: + return false; + } + + // not reached +} + +static inline bool json_isspace(int ch) +{ + switch (ch) { + case 0x20: + case 0x09: + case 0x0a: + case 0x0d: + return true; + + default: + return false; + } + + // not reached +} + +extern const UniValue NullUniValue; + +const UniValue& find_value( const UniValue& obj, const std::string& name); + +#endif // __UNIVALUE_H__ diff --git a/lib/.gitignore b/lib/.gitignore new file mode 100644 index 00000000..ee7fc285 --- /dev/null +++ b/lib/.gitignore @@ -0,0 +1,2 @@ +gen +.libs diff --git a/lib/univalue.cpp b/lib/univalue.cpp new file mode 100644 index 00000000..47ca7aca --- /dev/null +++ b/lib/univalue.cpp @@ -0,0 +1,359 @@ +// Copyright 2014 BitPay Inc. +// Copyright 2015 Bitcoin Core Developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "univalue.h" + +namespace +{ +static bool ParsePrechecks(const std::string& str) +{ + if (str.empty()) // No empty string allowed + return false; + if (str.size() >= 1 && (json_isspace(str[0]) || json_isspace(str[str.size()-1]))) // No padding allowed + return false; + if (str.size() != strlen(str.c_str())) // No embedded NUL characters allowed + return false; + return true; +} + +bool ParseInt32(const std::string& str, int32_t *out) +{ + if (!ParsePrechecks(str)) + return false; + char *endp = NULL; + errno = 0; // strtol will not set errno if valid + long int n = strtol(str.c_str(), &endp, 10); + if(out) *out = (int32_t)n; + // Note that strtol returns a *long int*, so even if strtol doesn't report a over/underflow + // we still have to check that the returned value is within the range of an *int32_t*. On 64-bit + // platforms the size of these types may be different. + return endp && *endp == 0 && !errno && + n >= std::numeric_limits::min() && + n <= std::numeric_limits::max(); +} + +bool ParseInt64(const std::string& str, int64_t *out) +{ + if (!ParsePrechecks(str)) + return false; + char *endp = NULL; + errno = 0; // strtoll will not set errno if valid + long long int n = strtoll(str.c_str(), &endp, 10); + if(out) *out = (int64_t)n; + // Note that strtoll returns a *long long int*, so even if strtol doesn't report a over/underflow + // we still have to check that the returned value is within the range of an *int64_t*. + return endp && *endp == 0 && !errno && + n >= std::numeric_limits::min() && + n <= std::numeric_limits::max(); +} + +bool ParseDouble(const std::string& str, double *out) +{ + if (!ParsePrechecks(str)) + return false; + if (str.size() >= 2 && str[0] == '0' && str[1] == 'x') // No hexadecimal floats allowed + return false; + std::istringstream text(str); + text.imbue(std::locale::classic()); + double result; + text >> result; + if(out) *out = result; + return text.eof() && !text.fail(); +} +} + +using namespace std; + +const UniValue NullUniValue; + +void UniValue::clear() +{ + typ = VNULL; + val.clear(); + keys.clear(); + values.clear(); +} + +bool UniValue::setNull() +{ + clear(); + return true; +} + +bool UniValue::setBool(bool val_) +{ + clear(); + typ = VBOOL; + if (val_) + val = "1"; + return true; +} + +static bool validNumStr(const string& s) +{ + string tokenVal; + unsigned int consumed; + enum jtokentype tt = getJsonToken(tokenVal, consumed, s.data(), s.data() + s.size()); + return (tt == JTOK_NUMBER); +} + +bool UniValue::setNumStr(const string& val_) +{ + if (!validNumStr(val_)) + return false; + + clear(); + typ = VNUM; + val = val_; + return true; +} + +bool UniValue::setInt(uint64_t val_) +{ + ostringstream oss; + + oss << val_; + + return setNumStr(oss.str()); +} + +bool UniValue::setInt(int64_t val_) +{ + ostringstream oss; + + oss << val_; + + return setNumStr(oss.str()); +} + +bool UniValue::setFloat(double val_) +{ + ostringstream oss; + + oss << std::setprecision(16) << val_; + + bool ret = setNumStr(oss.str()); + typ = VNUM; + return ret; +} + +bool UniValue::setStr(const string& val_) +{ + clear(); + typ = VSTR; + val = val_; + return true; +} + +bool UniValue::setArray() +{ + clear(); + typ = VARR; + return true; +} + +bool UniValue::setObject() +{ + clear(); + typ = VOBJ; + return true; +} + +bool UniValue::push_back(const UniValue& val_) +{ + if (typ != VARR) + return false; + + values.push_back(val_); + return true; +} + +bool UniValue::push_backV(const std::vector& vec) +{ + if (typ != VARR) + return false; + + values.insert(values.end(), vec.begin(), vec.end()); + + return true; +} + +bool UniValue::pushKV(const std::string& key, const UniValue& val_) +{ + if (typ != VOBJ) + return false; + + keys.push_back(key); + values.push_back(val_); + return true; +} + +bool UniValue::pushKVs(const UniValue& obj) +{ + if (typ != VOBJ || obj.typ != VOBJ) + return false; + + for (unsigned int i = 0; i < obj.keys.size(); i++) { + keys.push_back(obj.keys[i]); + values.push_back(obj.values.at(i)); + } + + return true; +} + +bool UniValue::findKey(const std::string& key, size_t& ret) const +{ + for (size_t i = 0; i < keys.size(); i++) { + if (keys[i] == key) { + ret = i; + return true; + } + } + + return false; +} + +bool UniValue::checkObject(const std::map& t) +{ + for (std::map::const_iterator it = t.begin(); + it != t.end(); ++it) { + size_t idx; + if (!findKey(it->first, idx)) + return false; + + if (values.at(idx).getType() != it->second) + return false; + } + + return true; +} + +const UniValue& UniValue::operator[](const std::string& key) const +{ + if (typ != VOBJ) + return NullUniValue; + + size_t index; + if (!findKey(key, index)) + return NullUniValue; + + return values.at(index); +} + +const UniValue& UniValue::operator[](size_t index) const +{ + if (typ != VOBJ && typ != VARR) + return NullUniValue; + if (index >= values.size()) + return NullUniValue; + + return values.at(index); +} + +const char *uvTypeName(UniValue::VType t) +{ + switch (t) { + case UniValue::VNULL: return "null"; + case UniValue::VBOOL: return "bool"; + case UniValue::VOBJ: return "object"; + case UniValue::VARR: return "array"; + case UniValue::VSTR: return "string"; + case UniValue::VNUM: return "number"; + } + + // not reached + return NULL; +} + +const UniValue& find_value(const UniValue& obj, const std::string& name) +{ + for (unsigned int i = 0; i < obj.keys.size(); i++) + if (obj.keys[i] == name) + return obj.values.at(i); + + return NullUniValue; +} + +const std::vector& UniValue::getKeys() const +{ + if (typ != VOBJ) + throw std::runtime_error("JSON value is not an object as expected"); + return keys; +} + +const std::vector& UniValue::getValues() const +{ + if (typ != VOBJ && typ != VARR) + throw std::runtime_error("JSON value is not an object or array as expected"); + return values; +} + +bool UniValue::get_bool() const +{ + if (typ != VBOOL) + throw std::runtime_error("JSON value is not a boolean as expected"); + return getBool(); +} + +const std::string& UniValue::get_str() const +{ + if (typ != VSTR) + throw std::runtime_error("JSON value is not a string as expected"); + return getValStr(); +} + +int UniValue::get_int() const +{ + if (typ != VNUM) + throw std::runtime_error("JSON value is not an integer as expected"); + int32_t retval; + if (!ParseInt32(getValStr(), &retval)) + throw std::runtime_error("JSON integer out of range"); + return retval; +} + +int64_t UniValue::get_int64() const +{ + if (typ != VNUM) + throw std::runtime_error("JSON value is not an integer as expected"); + int64_t retval; + if (!ParseInt64(getValStr(), &retval)) + throw std::runtime_error("JSON integer out of range"); + return retval; +} + +double UniValue::get_real() const +{ + if (typ != VNUM) + throw std::runtime_error("JSON value is not a number as expected"); + double retval; + if (!ParseDouble(getValStr(), &retval)) + throw std::runtime_error("JSON double out of range"); + return retval; +} + +const UniValue& UniValue::get_obj() const +{ + if (typ != VOBJ) + throw std::runtime_error("JSON value is not an object as expected"); + return *this; +} + +const UniValue& UniValue::get_array() const +{ + if (typ != VARR) + throw std::runtime_error("JSON value is not an array as expected"); + return *this; +} + diff --git a/lib/univalue_escapes.h b/lib/univalue_escapes.h new file mode 100644 index 00000000..74596aab --- /dev/null +++ b/lib/univalue_escapes.h @@ -0,0 +1,262 @@ +// Automatically generated file. Do not modify. +#ifndef BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H +#define BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H +static const char *escapes[256] = { + "\\u0000", + "\\u0001", + "\\u0002", + "\\u0003", + "\\u0004", + "\\u0005", + "\\u0006", + "\\u0007", + "\\b", + "\\t", + "\\n", + "\\u000b", + "\\f", + "\\r", + "\\u000e", + "\\u000f", + "\\u0010", + "\\u0011", + "\\u0012", + "\\u0013", + "\\u0014", + "\\u0015", + "\\u0016", + "\\u0017", + "\\u0018", + "\\u0019", + "\\u001a", + "\\u001b", + "\\u001c", + "\\u001d", + "\\u001e", + "\\u001f", + NULL, + NULL, + "\\\"", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + "\\\\", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + "\\u007f", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; +#endif // BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H diff --git a/lib/univalue_read.cpp b/lib/univalue_read.cpp new file mode 100644 index 00000000..7a9acdd7 --- /dev/null +++ b/lib/univalue_read.cpp @@ -0,0 +1,454 @@ +// Copyright 2014 BitPay Inc. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include "univalue.h" +#include "univalue_utffilter.h" + +using namespace std; + +static bool json_isdigit(int ch) +{ + return ((ch >= '0') && (ch <= '9')); +} + +// convert hexadecimal string to unsigned integer +static const char *hatoui(const char *first, const char *last, + unsigned int& out) +{ + unsigned int result = 0; + for (; first != last; ++first) + { + int digit; + if (json_isdigit(*first)) + digit = *first - '0'; + + else if (*first >= 'a' && *first <= 'f') + digit = *first - 'a' + 10; + + else if (*first >= 'A' && *first <= 'F') + digit = *first - 'A' + 10; + + else + break; + + result = 16 * result + digit; + } + out = result; + + return first; +} + +enum jtokentype getJsonToken(string& tokenVal, unsigned int& consumed, + const char *raw, const char *end) +{ + tokenVal.clear(); + consumed = 0; + + const char *rawStart = raw; + + while (raw < end && (json_isspace(*raw))) // skip whitespace + raw++; + + if (raw >= end) + return JTOK_NONE; + + switch (*raw) { + + case '{': + raw++; + consumed = (raw - rawStart); + return JTOK_OBJ_OPEN; + case '}': + raw++; + consumed = (raw - rawStart); + return JTOK_OBJ_CLOSE; + case '[': + raw++; + consumed = (raw - rawStart); + return JTOK_ARR_OPEN; + case ']': + raw++; + consumed = (raw - rawStart); + return JTOK_ARR_CLOSE; + + case ':': + raw++; + consumed = (raw - rawStart); + return JTOK_COLON; + case ',': + raw++; + consumed = (raw - rawStart); + return JTOK_COMMA; + + case 'n': + case 't': + case 'f': + if (!strncmp(raw, "null", 4)) { + raw += 4; + consumed = (raw - rawStart); + return JTOK_KW_NULL; + } else if (!strncmp(raw, "true", 4)) { + raw += 4; + consumed = (raw - rawStart); + return JTOK_KW_TRUE; + } else if (!strncmp(raw, "false", 5)) { + raw += 5; + consumed = (raw - rawStart); + return JTOK_KW_FALSE; + } else + return JTOK_ERR; + + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + // part 1: int + string numStr; + + const char *first = raw; + + const char *firstDigit = first; + if (!json_isdigit(*firstDigit)) + firstDigit++; + if ((*firstDigit == '0') && json_isdigit(firstDigit[1])) + return JTOK_ERR; + + numStr += *raw; // copy first char + raw++; + + if ((*first == '-') && (raw < end) && (!json_isdigit(*raw))) + return JTOK_ERR; + + while (raw < end && json_isdigit(*raw)) { // copy digits + numStr += *raw; + raw++; + } + + // part 2: frac + if (raw < end && *raw == '.') { + numStr += *raw; // copy . + raw++; + + if (raw >= end || !json_isdigit(*raw)) + return JTOK_ERR; + while (raw < end && json_isdigit(*raw)) { // copy digits + numStr += *raw; + raw++; + } + } + + // part 3: exp + if (raw < end && (*raw == 'e' || *raw == 'E')) { + numStr += *raw; // copy E + raw++; + + if (raw < end && (*raw == '-' || *raw == '+')) { // copy +/- + numStr += *raw; + raw++; + } + + if (raw >= end || !json_isdigit(*raw)) + return JTOK_ERR; + while (raw < end && json_isdigit(*raw)) { // copy digits + numStr += *raw; + raw++; + } + } + + tokenVal = numStr; + consumed = (raw - rawStart); + return JTOK_NUMBER; + } + + case '"': { + raw++; // skip " + + string valStr; + JSONUTF8StringFilter writer(valStr); + + while (raw < end) { + if ((unsigned char)*raw < 0x20) + return JTOK_ERR; + + else if (*raw == '\\') { + raw++; // skip backslash + + if (raw >= end) + return JTOK_ERR; + + switch (*raw) { + case '"': writer.push_back('\"'); break; + case '\\': writer.push_back('\\'); break; + case '/': writer.push_back('/'); break; + case 'b': writer.push_back('\b'); break; + case 'f': writer.push_back('\f'); break; + case 'n': writer.push_back('\n'); break; + case 'r': writer.push_back('\r'); break; + case 't': writer.push_back('\t'); break; + + case 'u': { + unsigned int codepoint; + if (raw + 1 + 4 >= end || + hatoui(raw + 1, raw + 1 + 4, codepoint) != + raw + 1 + 4) + return JTOK_ERR; + writer.push_back_u(codepoint); + raw += 4; + break; + } + default: + return JTOK_ERR; + + } + + raw++; // skip esc'd char + } + + else if (*raw == '"') { + raw++; // skip " + break; // stop scanning + } + + else { + writer.push_back(*raw); + raw++; + } + } + + if (!writer.finalize()) + return JTOK_ERR; + tokenVal = valStr; + consumed = (raw - rawStart); + return JTOK_STRING; + } + + default: + return JTOK_ERR; + } +} + +enum expect_bits { + EXP_OBJ_NAME = (1U << 0), + EXP_COLON = (1U << 1), + EXP_ARR_VALUE = (1U << 2), + EXP_VALUE = (1U << 3), + EXP_NOT_VALUE = (1U << 4), +}; + +#define expect(bit) (expectMask & (EXP_##bit)) +#define setExpect(bit) (expectMask |= EXP_##bit) +#define clearExpect(bit) (expectMask &= ~EXP_##bit) + +bool UniValue::read(const char *raw, size_t size) +{ + clear(); + + uint32_t expectMask = 0; + vector stack; + + string tokenVal; + unsigned int consumed; + enum jtokentype tok = JTOK_NONE; + enum jtokentype last_tok = JTOK_NONE; + const char* end = raw + size; + do { + last_tok = tok; + + tok = getJsonToken(tokenVal, consumed, raw, end); + if (tok == JTOK_NONE || tok == JTOK_ERR) + return false; + raw += consumed; + + bool isValueOpen = jsonTokenIsValue(tok) || + tok == JTOK_OBJ_OPEN || tok == JTOK_ARR_OPEN; + + if (expect(VALUE)) { + if (!isValueOpen) + return false; + clearExpect(VALUE); + + } else if (expect(ARR_VALUE)) { + bool isArrValue = isValueOpen || (tok == JTOK_ARR_CLOSE); + if (!isArrValue) + return false; + + clearExpect(ARR_VALUE); + + } else if (expect(OBJ_NAME)) { + bool isObjName = (tok == JTOK_OBJ_CLOSE || tok == JTOK_STRING); + if (!isObjName) + return false; + + } else if (expect(COLON)) { + if (tok != JTOK_COLON) + return false; + clearExpect(COLON); + + } else if (!expect(COLON) && (tok == JTOK_COLON)) { + return false; + } + + if (expect(NOT_VALUE)) { + if (isValueOpen) + return false; + clearExpect(NOT_VALUE); + } + + switch (tok) { + + case JTOK_OBJ_OPEN: + case JTOK_ARR_OPEN: { + VType utyp = (tok == JTOK_OBJ_OPEN ? VOBJ : VARR); + if (!stack.size()) { + if (utyp == VOBJ) + setObject(); + else + setArray(); + stack.push_back(this); + } else { + UniValue tmpVal(utyp); + UniValue *top = stack.back(); + top->values.push_back(tmpVal); + + UniValue *newTop = &(top->values.back()); + stack.push_back(newTop); + } + + if (utyp == VOBJ) + setExpect(OBJ_NAME); + else + setExpect(ARR_VALUE); + break; + } + + case JTOK_OBJ_CLOSE: + case JTOK_ARR_CLOSE: { + if (!stack.size() || (last_tok == JTOK_COMMA)) + return false; + + VType utyp = (tok == JTOK_OBJ_CLOSE ? VOBJ : VARR); + UniValue *top = stack.back(); + if (utyp != top->getType()) + return false; + + stack.pop_back(); + clearExpect(OBJ_NAME); + setExpect(NOT_VALUE); + break; + } + + case JTOK_COLON: { + if (!stack.size()) + return false; + + UniValue *top = stack.back(); + if (top->getType() != VOBJ) + return false; + + setExpect(VALUE); + break; + } + + case JTOK_COMMA: { + if (!stack.size() || + (last_tok == JTOK_COMMA) || (last_tok == JTOK_ARR_OPEN)) + return false; + + UniValue *top = stack.back(); + if (top->getType() == VOBJ) + setExpect(OBJ_NAME); + else + setExpect(ARR_VALUE); + break; + } + + case JTOK_KW_NULL: + case JTOK_KW_TRUE: + case JTOK_KW_FALSE: { + UniValue tmpVal; + switch (tok) { + case JTOK_KW_NULL: + // do nothing more + break; + case JTOK_KW_TRUE: + tmpVal.setBool(true); + break; + case JTOK_KW_FALSE: + tmpVal.setBool(false); + break; + default: /* impossible */ break; + } + + if (!stack.size()) { + *this = tmpVal; + break; + } + + UniValue *top = stack.back(); + top->values.push_back(tmpVal); + + setExpect(NOT_VALUE); + break; + } + + case JTOK_NUMBER: { + UniValue tmpVal(VNUM, tokenVal); + if (!stack.size()) { + *this = tmpVal; + break; + } + + UniValue *top = stack.back(); + top->values.push_back(tmpVal); + + setExpect(NOT_VALUE); + break; + } + + case JTOK_STRING: { + if (expect(OBJ_NAME)) { + UniValue *top = stack.back(); + top->keys.push_back(tokenVal); + clearExpect(OBJ_NAME); + setExpect(COLON); + } else { + UniValue tmpVal(VSTR, tokenVal); + if (!stack.size()) { + *this = tmpVal; + break; + } + UniValue *top = stack.back(); + top->values.push_back(tmpVal); + } + + setExpect(NOT_VALUE); + break; + } + + default: + return false; + } + } while (!stack.empty ()); + + /* Check that nothing follows the initial construct (parsed above). */ + tok = getJsonToken(tokenVal, consumed, raw, end); + if (tok != JTOK_NONE) + return false; + + return true; +} + +bool UniValue::read(const char *raw) { + return read(raw, strlen(raw)); +} diff --git a/lib/univalue_utffilter.h b/lib/univalue_utffilter.h new file mode 100644 index 00000000..b4a9ddc0 --- /dev/null +++ b/lib/univalue_utffilter.h @@ -0,0 +1,119 @@ +// Copyright 2016 Wladimir J. van der Laan +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#ifndef UNIVALUE_UTFFILTER_H +#define UNIVALUE_UTFFILTER_H + +#include + +/** + * Filter that generates and validates UTF-8, as well as collates UTF-16 + * surrogate pairs as specified in RFC4627. + */ +class JSONUTF8StringFilter +{ +public: + JSONUTF8StringFilter(std::string &s): + str(s), is_valid(true), codepoint(0), state(0), surpair(0) + { + } + // Write single 8-bit char (may be part of UTF-8 sequence) + void push_back(unsigned char ch) + { + if (state == 0) { + if (ch < 0x80) // 7-bit ASCII, fast direct pass-through + str.push_back(ch); + else if (ch < 0xc0) // Mid-sequence character, invalid in this state + is_valid = false; + else if (ch < 0xe0) { // Start of 2-byte sequence + codepoint = (ch & 0x1f) << 6; + state = 6; + } else if (ch < 0xf0) { // Start of 3-byte sequence + codepoint = (ch & 0x0f) << 12; + state = 12; + } else if (ch < 0xf8) { // Start of 4-byte sequence + codepoint = (ch & 0x07) << 18; + state = 18; + } else // Reserved, invalid + is_valid = false; + } else { + if ((ch & 0xc0) != 0x80) // Not a continuation, invalid + is_valid = false; + state -= 6; + codepoint |= (ch & 0x3f) << state; + if (state == 0) + push_back_u(codepoint); + } + } + // Write codepoint directly, possibly collating surrogate pairs + void push_back_u(unsigned int codepoint_) + { + if (state) // Only accept full codepoints in open state + is_valid = false; + if (codepoint_ >= 0xD800 && codepoint_ < 0xDC00) { // First half of surrogate pair + if (surpair) // Two subsequent surrogate pair openers - fail + is_valid = false; + else + surpair = codepoint_; + } else if (codepoint_ >= 0xDC00 && codepoint_ < 0xE000) { // Second half of surrogate pair + if (surpair) { // Open surrogate pair, expect second half + // Compute code point from UTF-16 surrogate pair + append_codepoint(0x10000 | ((surpair - 0xD800)<<10) | (codepoint_ - 0xDC00)); + surpair = 0; + } else // Second half doesn't follow a first half - fail + is_valid = false; + } else { + if (surpair) // First half of surrogate pair not followed by second - fail + is_valid = false; + else + append_codepoint(codepoint_); + } + } + // Check that we're in a state where the string can be ended + // No open sequences, no open surrogate pairs, etc + bool finalize() + { + if (state || surpair) + is_valid = false; + return is_valid; + } +private: + std::string &str; + bool is_valid; + // Current UTF-8 decoding state + unsigned int codepoint; + int state; // Top bit to be filled in for next UTF-8 byte, or 0 + + // Keep track of the following state to handle the following section of + // RFC4627: + // + // To escape an extended character that is not in the Basic Multilingual + // Plane, the character is represented as a twelve-character sequence, + // encoding the UTF-16 surrogate pair. So, for example, a string + // containing only the G clef character (U+1D11E) may be represented as + // "\uD834\uDD1E". + // + // Two subsequent \u.... may have to be replaced with one actual codepoint. + unsigned int surpair; // First half of open UTF-16 surrogate pair, or 0 + + void append_codepoint(unsigned int codepoint_) + { + if (codepoint_ <= 0x7f) + str.push_back((char)codepoint_); + else if (codepoint_ <= 0x7FF) { + str.push_back((char)(0xC0 | (codepoint_ >> 6))); + str.push_back((char)(0x80 | (codepoint_ & 0x3F))); + } else if (codepoint_ <= 0xFFFF) { + str.push_back((char)(0xE0 | (codepoint_ >> 12))); + str.push_back((char)(0x80 | ((codepoint_ >> 6) & 0x3F))); + str.push_back((char)(0x80 | (codepoint_ & 0x3F))); + } else if (codepoint_ <= 0x1FFFFF) { + str.push_back((char)(0xF0 | (codepoint_ >> 18))); + str.push_back((char)(0x80 | ((codepoint_ >> 12) & 0x3F))); + str.push_back((char)(0x80 | ((codepoint_ >> 6) & 0x3F))); + str.push_back((char)(0x80 | (codepoint_ & 0x3F))); + } + } +}; + +#endif diff --git a/lib/univalue_write.cpp b/lib/univalue_write.cpp new file mode 100644 index 00000000..cf278359 --- /dev/null +++ b/lib/univalue_write.cpp @@ -0,0 +1,115 @@ +// Copyright 2014 BitPay Inc. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include "univalue.h" +#include "univalue_escapes.h" + +using namespace std; + +static string json_escape(const string& inS) +{ + string outS; + outS.reserve(inS.size() * 2); + + for (unsigned int i = 0; i < inS.size(); i++) { + unsigned char ch = inS[i]; + const char *escStr = escapes[ch]; + + if (escStr) + outS += escStr; + else + outS += ch; + } + + return outS; +} + +string UniValue::write(unsigned int prettyIndent, + unsigned int indentLevel) const +{ + string s; + s.reserve(1024); + + unsigned int modIndent = indentLevel; + if (modIndent == 0) + modIndent = 1; + + switch (typ) { + case VNULL: + s += "null"; + break; + case VOBJ: + writeObject(prettyIndent, modIndent, s); + break; + case VARR: + writeArray(prettyIndent, modIndent, s); + break; + case VSTR: + s += "\"" + json_escape(val) + "\""; + break; + case VNUM: + s += val; + break; + case VBOOL: + s += (val == "1" ? "true" : "false"); + break; + } + + return s; +} + +static void indentStr(unsigned int prettyIndent, unsigned int indentLevel, string& s) +{ + s.append(prettyIndent * indentLevel, ' '); +} + +void UniValue::writeArray(unsigned int prettyIndent, unsigned int indentLevel, string& s) const +{ + s += "["; + if (prettyIndent) + s += "\n"; + + for (unsigned int i = 0; i < values.size(); i++) { + if (prettyIndent) + indentStr(prettyIndent, indentLevel, s); + s += values[i].write(prettyIndent, indentLevel + 1); + if (i != (values.size() - 1)) { + s += ","; + } + if (prettyIndent) + s += "\n"; + } + + if (prettyIndent) + indentStr(prettyIndent, indentLevel - 1, s); + s += "]"; +} + +void UniValue::writeObject(unsigned int prettyIndent, unsigned int indentLevel, string& s) const +{ + s += "{"; + if (prettyIndent) + s += "\n"; + + for (unsigned int i = 0; i < keys.size(); i++) { + if (prettyIndent) + indentStr(prettyIndent, indentLevel, s); + s += "\"" + json_escape(keys[i]) + "\":"; + if (prettyIndent) + s += " "; + s += values.at(i).write(prettyIndent, indentLevel + 1); + if (i != (values.size() - 1)) + s += ","; + if (prettyIndent) + s += "\n"; + } + + if (prettyIndent) + indentStr(prettyIndent, indentLevel - 1, s); + s += "}"; +} + diff --git a/pc/libunivalue-uninstalled.pc.in b/pc/libunivalue-uninstalled.pc.in new file mode 100644 index 00000000..b7f53e87 --- /dev/null +++ b/pc/libunivalue-uninstalled.pc.in @@ -0,0 +1,9 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libunivalue +Description: libunivalue, C++ universal value object and JSON library +Version: @VERSION@ +Libs: ${pc_top_builddir}/${pcfiledir}/libunivalue.la diff --git a/pc/libunivalue.pc.in b/pc/libunivalue.pc.in new file mode 100644 index 00000000..358a2d5f --- /dev/null +++ b/pc/libunivalue.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libunivalue +Description: libunivalue, C++ universal value object and JSON library +Version: @VERSION@ +Libs: -L${libdir} -lunivalue +Cflags: -I${includedir} diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 00000000..9d0ed570 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,6 @@ +unitester +test_json +no_nul + +*.trs +*.log diff --git a/test/fail1.json b/test/fail1.json new file mode 100644 index 00000000..8feb01a6 --- /dev/null +++ b/test/fail1.json @@ -0,0 +1 @@ +"This is a string that never ends, yes it goes on and on, my friends. diff --git a/test/fail10.json b/test/fail10.json new file mode 100644 index 00000000..5d8c0047 --- /dev/null +++ b/test/fail10.json @@ -0,0 +1 @@ +{"Extra value after close": true} "misplaced quoted value" \ No newline at end of file diff --git a/test/fail11.json b/test/fail11.json new file mode 100644 index 00000000..76eb95b4 --- /dev/null +++ b/test/fail11.json @@ -0,0 +1 @@ +{"Illegal expression": 1 + 2} \ No newline at end of file diff --git a/test/fail12.json b/test/fail12.json new file mode 100644 index 00000000..77580a45 --- /dev/null +++ b/test/fail12.json @@ -0,0 +1 @@ +{"Illegal invocation": alert()} \ No newline at end of file diff --git a/test/fail13.json b/test/fail13.json new file mode 100644 index 00000000..379406b5 --- /dev/null +++ b/test/fail13.json @@ -0,0 +1 @@ +{"Numbers cannot have leading zeroes": 013} \ No newline at end of file diff --git a/test/fail14.json b/test/fail14.json new file mode 100644 index 00000000..0ed366b3 --- /dev/null +++ b/test/fail14.json @@ -0,0 +1 @@ +{"Numbers cannot be hex": 0x14} \ No newline at end of file diff --git a/test/fail15.json b/test/fail15.json new file mode 100644 index 00000000..fc8376b6 --- /dev/null +++ b/test/fail15.json @@ -0,0 +1 @@ +["Illegal backslash escape: \x15"] \ No newline at end of file diff --git a/test/fail16.json b/test/fail16.json new file mode 100644 index 00000000..3fe21d4b --- /dev/null +++ b/test/fail16.json @@ -0,0 +1 @@ +[\naked] \ No newline at end of file diff --git a/test/fail17.json b/test/fail17.json new file mode 100644 index 00000000..62b9214a --- /dev/null +++ b/test/fail17.json @@ -0,0 +1 @@ +["Illegal backslash escape: \017"] \ No newline at end of file diff --git a/test/fail18.json b/test/fail18.json new file mode 100644 index 00000000..edac9271 --- /dev/null +++ b/test/fail18.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/test/fail19.json b/test/fail19.json new file mode 100644 index 00000000..3b9c46fa --- /dev/null +++ b/test/fail19.json @@ -0,0 +1 @@ +{"Missing colon" null} \ No newline at end of file diff --git a/test/fail2.json b/test/fail2.json new file mode 100644 index 00000000..6b7c11e5 --- /dev/null +++ b/test/fail2.json @@ -0,0 +1 @@ +["Unclosed array" \ No newline at end of file diff --git a/test/fail20.json b/test/fail20.json new file mode 100644 index 00000000..27c1af3e --- /dev/null +++ b/test/fail20.json @@ -0,0 +1 @@ +{"Double colon":: null} \ No newline at end of file diff --git a/test/fail21.json b/test/fail21.json new file mode 100644 index 00000000..62474573 --- /dev/null +++ b/test/fail21.json @@ -0,0 +1 @@ +{"Comma instead of colon", null} \ No newline at end of file diff --git a/test/fail22.json b/test/fail22.json new file mode 100644 index 00000000..a7752581 --- /dev/null +++ b/test/fail22.json @@ -0,0 +1 @@ +["Colon instead of comma": false] \ No newline at end of file diff --git a/test/fail23.json b/test/fail23.json new file mode 100644 index 00000000..494add1c --- /dev/null +++ b/test/fail23.json @@ -0,0 +1 @@ +["Bad value", truth] \ No newline at end of file diff --git a/test/fail24.json b/test/fail24.json new file mode 100644 index 00000000..caff239b --- /dev/null +++ b/test/fail24.json @@ -0,0 +1 @@ +['single quote'] \ No newline at end of file diff --git a/test/fail25.json b/test/fail25.json new file mode 100644 index 00000000..8b7ad23e --- /dev/null +++ b/test/fail25.json @@ -0,0 +1 @@ +[" tab character in string "] \ No newline at end of file diff --git a/test/fail26.json b/test/fail26.json new file mode 100644 index 00000000..845d26a6 --- /dev/null +++ b/test/fail26.json @@ -0,0 +1 @@ +["tab\ character\ in\ string\ "] \ No newline at end of file diff --git a/test/fail27.json b/test/fail27.json new file mode 100644 index 00000000..6b01a2ca --- /dev/null +++ b/test/fail27.json @@ -0,0 +1,2 @@ +["line +break"] \ No newline at end of file diff --git a/test/fail28.json b/test/fail28.json new file mode 100644 index 00000000..621a0101 --- /dev/null +++ b/test/fail28.json @@ -0,0 +1,2 @@ +["line\ +break"] \ No newline at end of file diff --git a/test/fail29.json b/test/fail29.json new file mode 100644 index 00000000..47ec421b --- /dev/null +++ b/test/fail29.json @@ -0,0 +1 @@ +[0e] \ No newline at end of file diff --git a/test/fail3.json b/test/fail3.json new file mode 100644 index 00000000..168c81eb --- /dev/null +++ b/test/fail3.json @@ -0,0 +1 @@ +{unquoted_key: "keys must be quoted"} \ No newline at end of file diff --git a/test/fail30.json b/test/fail30.json new file mode 100644 index 00000000..8ab0bc4b --- /dev/null +++ b/test/fail30.json @@ -0,0 +1 @@ +[0e+] \ No newline at end of file diff --git a/test/fail31.json b/test/fail31.json new file mode 100644 index 00000000..1cce602b --- /dev/null +++ b/test/fail31.json @@ -0,0 +1 @@ +[0e+-1] \ No newline at end of file diff --git a/test/fail32.json b/test/fail32.json new file mode 100644 index 00000000..45cba739 --- /dev/null +++ b/test/fail32.json @@ -0,0 +1 @@ +{"Comma instead if closing brace": true, \ No newline at end of file diff --git a/test/fail33.json b/test/fail33.json new file mode 100644 index 00000000..ca5eb19d --- /dev/null +++ b/test/fail33.json @@ -0,0 +1 @@ +["mismatch"} \ No newline at end of file diff --git a/test/fail34.json b/test/fail34.json new file mode 100644 index 00000000..3f8be172 --- /dev/null +++ b/test/fail34.json @@ -0,0 +1 @@ +{} garbage \ No newline at end of file diff --git a/test/fail35.json b/test/fail35.json new file mode 100644 index 00000000..de30ca5c --- /dev/null +++ b/test/fail35.json @@ -0,0 +1 @@ +[ true true true [] [] [] ] diff --git a/test/fail36.json b/test/fail36.json new file mode 100644 index 00000000..f82eb8e1 --- /dev/null +++ b/test/fail36.json @@ -0,0 +1 @@ +{"a":} diff --git a/test/fail37.json b/test/fail37.json new file mode 100644 index 00000000..3294dc3a --- /dev/null +++ b/test/fail37.json @@ -0,0 +1 @@ +{"a":1 "b":2} diff --git a/test/fail38.json b/test/fail38.json new file mode 100644 index 00000000..b245e2e4 --- /dev/null +++ b/test/fail38.json @@ -0,0 +1 @@ +["\ud834"] diff --git a/test/fail39.json b/test/fail39.json new file mode 100644 index 00000000..7c9e263f --- /dev/null +++ b/test/fail39.json @@ -0,0 +1 @@ +["\udd61"] diff --git a/test/fail4.json b/test/fail4.json new file mode 100644 index 00000000..9de168bf --- /dev/null +++ b/test/fail4.json @@ -0,0 +1 @@ +["extra comma",] \ No newline at end of file diff --git a/test/fail40.json b/test/fail40.json new file mode 100644 index 00000000..664dc9e2 --- /dev/null +++ b/test/fail40.json @@ -0,0 +1 @@ +["…¡"] \ No newline at end of file diff --git a/test/fail41.json b/test/fail41.json new file mode 100644 index 00000000..0de342a2 --- /dev/null +++ b/test/fail41.json @@ -0,0 +1 @@ +["ð…"] \ No newline at end of file diff --git a/test/fail42.json b/test/fail42.json new file mode 100644 index 0000000000000000000000000000000000000000..9c7565adbddf645df5edfbdcd630c7a0f94aa2eb GIT binary patch literal 37 kcma!6N=i-3FG^L&E6q_zsw_!Wie*qrOe;w(LWpny0Pvs;RsaA1 literal 0 HcmV?d00001 diff --git a/test/fail5.json b/test/fail5.json new file mode 100644 index 00000000..ddf3ce3d --- /dev/null +++ b/test/fail5.json @@ -0,0 +1 @@ +["double extra comma",,] \ No newline at end of file diff --git a/test/fail6.json b/test/fail6.json new file mode 100644 index 00000000..ed91580e --- /dev/null +++ b/test/fail6.json @@ -0,0 +1 @@ +[ , "<-- missing value"] \ No newline at end of file diff --git a/test/fail7.json b/test/fail7.json new file mode 100644 index 00000000..8a96af3e --- /dev/null +++ b/test/fail7.json @@ -0,0 +1 @@ +["Comma after the close"], \ No newline at end of file diff --git a/test/fail8.json b/test/fail8.json new file mode 100644 index 00000000..b28479c6 --- /dev/null +++ b/test/fail8.json @@ -0,0 +1 @@ +["Extra close"]] \ No newline at end of file diff --git a/test/fail9.json b/test/fail9.json new file mode 100644 index 00000000..5815574f --- /dev/null +++ b/test/fail9.json @@ -0,0 +1 @@ +{"Extra comma": true,} \ No newline at end of file diff --git a/test/no_nul.cpp b/test/no_nul.cpp new file mode 100644 index 00000000..83d29220 --- /dev/null +++ b/test/no_nul.cpp @@ -0,0 +1,8 @@ +#include "univalue.h" + +int main (int argc, char *argv[]) +{ + char buf[] = "___[1,2,3]___"; + UniValue val; + return val.read(buf + 3, 7) ? 0 : 1; +} diff --git a/test/pass1.json b/test/pass1.json new file mode 100644 index 00000000..70e26854 --- /dev/null +++ b/test/pass1.json @@ -0,0 +1,58 @@ +[ + "JSON Test Pattern pass1", + {"object with 1 member":["array with 1 element"]}, + {}, + [], + -42, + true, + false, + null, + { + "integer": 1234567890, + "real": -9876.543210, + "e": 0.123456789e-12, + "E": 1.234567890E+34, + "": 23456789012E66, + "zero": 0, + "one": 1, + "space": " ", + "quote": "\"", + "backslash": "\\", + "controls": "\b\f\n\r\t", + "slash": "/ & \/", + "alpha": "abcdefghijklmnopqrstuvwyz", + "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", + "digit": "0123456789", + "0123456789": "digit", + "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", + "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", + "true": true, + "false": false, + "null": null, + "array":[ ], + "object":{ }, + "address": "50 St. James Street", + "url": "http://www.JSON.org/", + "comment": "// /* */": " ", + " s p a c e d " :[1,2 , 3 + +, + +4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], + "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", + "quotes": "" \u0022 %22 0x22 034 "", + "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" +: "A key can be any string" + }, + 0.5 ,98.6 +, +99.44 +, + +1066, +1e1, +0.1e1, +1e-1, +1e00,2e+00,2e-00 +,"rosebud"] \ No newline at end of file diff --git a/test/pass2.json b/test/pass2.json new file mode 100644 index 00000000..d3c63c7a --- /dev/null +++ b/test/pass2.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/test/pass3.json b/test/pass3.json new file mode 100644 index 00000000..4528d51f --- /dev/null +++ b/test/pass3.json @@ -0,0 +1,6 @@ +{ + "JSON Test Pattern pass3": { + "The outermost value": "must be an object or array.", + "In this test": "It is an object." + } +} diff --git a/test/round1.json b/test/round1.json new file mode 100644 index 00000000..a711e730 --- /dev/null +++ b/test/round1.json @@ -0,0 +1 @@ +["\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f"] diff --git a/test/round2.json b/test/round2.json new file mode 100644 index 00000000..b766cccc --- /dev/null +++ b/test/round2.json @@ -0,0 +1 @@ +["a§■ðŽ’ð…¡"] diff --git a/test/round3.json b/test/round3.json new file mode 100644 index 00000000..7182dc2f --- /dev/null +++ b/test/round3.json @@ -0,0 +1 @@ +"abcdefghijklmnopqrstuvwxyz" diff --git a/test/round4.json b/test/round4.json new file mode 100644 index 00000000..7f8f011e --- /dev/null +++ b/test/round4.json @@ -0,0 +1 @@ +7 diff --git a/test/round5.json b/test/round5.json new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/test/round5.json @@ -0,0 +1 @@ +true diff --git a/test/round6.json b/test/round6.json new file mode 100644 index 00000000..c508d536 --- /dev/null +++ b/test/round6.json @@ -0,0 +1 @@ +false diff --git a/test/round7.json b/test/round7.json new file mode 100644 index 00000000..19765bd5 --- /dev/null +++ b/test/round7.json @@ -0,0 +1 @@ +null diff --git a/test/test_json.cpp b/test/test_json.cpp new file mode 100644 index 00000000..2943bae2 --- /dev/null +++ b/test/test_json.cpp @@ -0,0 +1,24 @@ +// Test program that can be called by the JSON test suite at +// https://github.com/nst/JSONTestSuite. +// +// It reads JSON input from stdin and exits with code 0 if it can be parsed +// successfully. It also pretty prints the parsed JSON value to stdout. + +#include +#include +#include "univalue.h" + +using namespace std; + +int main (int argc, char *argv[]) +{ + UniValue val; + if (val.read(string(istreambuf_iterator(cin), + istreambuf_iterator()))) { + cout << val.write(1 /* prettyIndent */, 4 /* indentLevel */) << endl; + return 0; + } else { + cerr << "JSON Parse Error." << endl; + return 1; + } +} diff --git a/test/unitester.cpp b/test/unitester.cpp new file mode 100644 index 00000000..aa6f91c1 --- /dev/null +++ b/test/unitester.cpp @@ -0,0 +1,169 @@ +// Copyright 2014 BitPay Inc. +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include +#include "univalue.h" + +#ifndef JSON_TEST_SRC +#error JSON_TEST_SRC must point to test source directory +#endif + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#endif + +using namespace std; +string srcdir(JSON_TEST_SRC); +static bool test_failed = false; + +#define d_assert(expr) { if (!(expr)) { test_failed = true; fprintf(stderr, "%s failed\n", filename.c_str()); } } +#define f_assert(expr) { if (!(expr)) { test_failed = true; fprintf(stderr, "%s failed\n", __func__); } } + +static std::string rtrim(std::string s) +{ + s.erase(s.find_last_not_of(" \n\r\t")+1); + return s; +} + +static void runtest(string filename, const string& jdata) +{ + string prefix = filename.substr(0, 4); + + bool wantPass = (prefix == "pass") || (prefix == "roun"); + bool wantFail = (prefix == "fail"); + bool wantRoundTrip = (prefix == "roun"); + assert(wantPass || wantFail); + + UniValue val; + bool testResult = val.read(jdata); + + if (wantPass) { + d_assert(testResult == true); + } else { + d_assert(testResult == false); + } + + if (wantRoundTrip) { + std::string odata = val.write(0, 0); + assert(odata == rtrim(jdata)); + } +} + +static void runtest_file(const char *filename_) +{ + string basename(filename_); + string filename = srcdir + "/" + basename; + FILE *f = fopen(filename.c_str(), "r"); + assert(f != NULL); + + string jdata; + + char buf[4096]; + while (!feof(f)) { + int bread = fread(buf, 1, sizeof(buf), f); + assert(!ferror(f)); + + string s(buf, bread); + jdata += s; + } + + assert(!ferror(f)); + fclose(f); + + runtest(basename, jdata); +} + +static const char *filenames[] = { + "fail10.json", + "fail11.json", + "fail12.json", + "fail13.json", + "fail14.json", + "fail15.json", + "fail16.json", + "fail17.json", + //"fail18.json", // investigate + "fail19.json", + "fail1.json", + "fail20.json", + "fail21.json", + "fail22.json", + "fail23.json", + "fail24.json", + "fail25.json", + "fail26.json", + "fail27.json", + "fail28.json", + "fail29.json", + "fail2.json", + "fail30.json", + "fail31.json", + "fail32.json", + "fail33.json", + "fail34.json", + "fail35.json", + "fail36.json", + "fail37.json", + "fail38.json", // invalid unicode: only first half of surrogate pair + "fail39.json", // invalid unicode: only second half of surrogate pair + "fail40.json", // invalid unicode: broken UTF-8 + "fail41.json", // invalid unicode: unfinished UTF-8 + "fail42.json", // valid json with garbage following a nul byte + "fail3.json", + "fail4.json", // extra comma + "fail5.json", + "fail6.json", + "fail7.json", + "fail8.json", + "fail9.json", // extra comma + "pass1.json", + "pass2.json", + "pass3.json", + "round1.json", // round-trip test + "round2.json", // unicode + "round3.json", // bare string + "round4.json", // bare number + "round5.json", // bare true + "round6.json", // bare false + "round7.json", // bare null +}; + +// Test \u handling +void unescape_unicode_test() +{ + UniValue val; + bool testResult; + // Escaped ASCII (quote) + testResult = val.read("[\"\\u0022\"]"); + f_assert(testResult); + f_assert(val[0].get_str() == "\""); + // Escaped Basic Plane character, two-byte UTF-8 + testResult = val.read("[\"\\u0191\"]"); + f_assert(testResult); + f_assert(val[0].get_str() == "\xc6\x91"); + // Escaped Basic Plane character, three-byte UTF-8 + testResult = val.read("[\"\\u2191\"]"); + f_assert(testResult); + f_assert(val[0].get_str() == "\xe2\x86\x91"); + // Escaped Supplementary Plane character U+1d161 + testResult = val.read("[\"\\ud834\\udd61\"]"); + f_assert(testResult); + f_assert(val[0].get_str() == "\xf0\x9d\x85\xa1"); +} + +int main (int argc, char *argv[]) +{ + for (unsigned int fidx = 0; fidx < ARRAY_SIZE(filenames); fidx++) { + runtest_file(filenames[fidx]); + } + + unescape_unicode_test(); + + return test_failed ? 1 : 0; +} + From a10a6e2a0ec291c39ca72963aa50e77cffc8f42a Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Fri, 4 Sep 2015 16:11:34 +0200 Subject: [PATCH 03/16] [Univalue] add univalue over subtree similar to secp256k1 include and compile univalue over a subtree --- configure.ac | 2 +- src/Makefile.am | 23 +++++++++-------------- src/Makefile.qt.include | 2 +- src/Makefile.qttest.include | 2 +- src/Makefile.test.include | 3 ++- src/bitcoin-cli.cpp | 2 +- src/bitcoin-tx.cpp | 2 +- src/core_read.cpp | 2 +- src/core_write.cpp | 2 +- src/qt/rpcconsole.cpp | 2 +- src/rest.cpp | 2 +- src/rpcblockchain.cpp | 2 +- src/rpcclient.cpp | 2 +- src/rpcclient.h | 2 +- src/rpcmining.cpp | 2 +- src/rpcmisc.cpp | 3 ++- src/rpcnet.cpp | 2 +- src/rpcprotocol.h | 2 +- src/rpcrawtransaction.cpp | 2 +- src/rpcserver.cpp | 2 +- src/rpcserver.h | 2 +- src/test/base58_tests.cpp | 2 +- src/test/rpc_tests.cpp | 2 +- src/test/rpc_wallet_tests.cpp | 2 +- src/test/script_tests.cpp | 2 +- src/test/sighash_tests.cpp | 2 +- src/test/transaction_tests.cpp | 2 +- src/test/univalue_tests.cpp | 2 +- src/wallet/rpcdump.cpp | 2 +- src/wallet/rpcwallet.cpp | 2 +- 30 files changed, 40 insertions(+), 43 deletions(-) diff --git a/configure.ac b/configure.ac index c1032d16..63f9b75f 100644 --- a/configure.ac +++ b/configure.ac @@ -1003,7 +1003,7 @@ unset PKG_CONFIG_LIBDIR PKG_CONFIG_LIBDIR="$PKGCONFIG_LIBDIR_TEMP" ac_configure_args="${ac_configure_args} --disable-shared --with-pic --with-bignum=no" -AC_CONFIG_SUBDIRS([src/secp256k1]) +AC_CONFIG_SUBDIRS([src/secp256k1 src/univalue]) AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am index 2c327467..1cd85e2f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,4 @@ -DIST_SUBDIRS = secp256k1 +DIST_SUBDIRS = secp256k1 univalue AM_LDFLAGS = $(PTHREAD_CFLAGS) $(LIBTOOL_LDFLAGS) @@ -21,6 +21,7 @@ BITCOIN_CONFIG_INCLUDES=-I$(builddir)/config BITCOIN_INCLUDES=-I$(builddir) -I$(builddir)/obj $(BOOST_CPPFLAGS) $(LEVELDB_CPPFLAGS) $(CRYPTO_CFLAGS) $(SSL_CFLAGS) BITCOIN_INCLUDES += -I$(srcdir)/secp256k1/include +BITCOIN_INCLUDES += -I$(srcdir)/univalue/include LIBBITCOIN_SERVER=libbitcoin_server.a LIBBITCOIN_WALLET=libbitcoin_wallet.a @@ -28,13 +29,16 @@ LIBBITCOIN_COMMON=libbitcoin_common.a LIBBITCOIN_CLI=libbitcoin_cli.a LIBBITCOIN_UTIL=libbitcoin_util.a LIBBITCOIN_CRYPTO=crypto/libbitcoin_crypto.a -LIBBITCOIN_UNIVALUE=univalue/libbitcoin_univalue.a LIBBITCOINQT=qt/libbitcoinqt.a LIBSECP256K1=secp256k1/libsecp256k1.la +LIBUNIVALUE=univalue/lib/libunivalue.la LIBZCASH=libzcash.a $(LIBSECP256K1): $(wildcard secp256k1/src/*) $(wildcard secp256k1/include/*) $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F) + +$(LIBUNIVALUE): $(wildcard univalue/lib/*) $(wildcard univalue/include/*) + $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F) # Make is not made aware of per-object dependencies to avoid limiting building parallelization # But to build the less dependent modules first, we manually select their order here: @@ -42,7 +46,6 @@ EXTRA_LIBRARIES = \ crypto/libbitcoin_crypto.a \ libbitcoin_util.a \ libbitcoin_common.a \ - univalue/libbitcoin_univalue.a \ libbitcoin_server.a \ libbitcoin_cli.a \ libzcash.a @@ -287,14 +290,6 @@ crypto_libbitcoin_crypto_a_SOURCES += \ ${EQUIHASH_TROMP_SOURCES} endif -# univalue JSON library -univalue_libbitcoin_univalue_a_SOURCES = \ - univalue/univalue.cpp \ - univalue/univalue.h \ - univalue/univalue_escapes.h \ - univalue/univalue_read.cpp \ - univalue/univalue_write.cpp - # common: shared between bitcoind, and bitcoin-qt and non-server tools libbitcoin_common_a_CPPFLAGS = $(BITCOIN_INCLUDES) libbitcoin_common_a_SOURCES = \ @@ -374,7 +369,7 @@ endif zcashd_LDADD = \ $(LIBBITCOIN_SERVER) \ $(LIBBITCOIN_COMMON) \ - $(LIBBITCOIN_UNIVALUE) \ + $(LIBUNIVALUE) \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_CRYPTO) \ $(LIBZCASH) \ @@ -411,7 +406,7 @@ endif zcash_cli_LDADD = \ $(LIBBITCOIN_CLI) \ - $(LIBBITCOIN_UNIVALUE) \ + $(LIBUNIVALUE) \ $(LIBBITCOIN_UTIL) \ $(BOOST_LIBS) \ $(SSL_LIBS) \ @@ -432,7 +427,7 @@ endif # FIXME: Is libzcash needed for zcash_tx? zcash_tx_LDADD = \ - $(LIBBITCOIN_UNIVALUE) \ + $(LIBUNIVALUE) \ $(LIBBITCOIN_COMMON) \ $(LIBBITCOIN_UTIL) \ $(LIBSECP256K1) \ diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 9513da08..5dde324a 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -364,7 +364,7 @@ endif if ENABLE_ZMQ qt_bitcoin_qt_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) endif -qt_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \ +qt_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \ $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) $(LIBZCASH_LIBS) qt_bitcoin_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) qt_bitcoin_qt_LIBTOOLFLAGS = --tag CXX diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index 128c3f6d..6a9d6b1a 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -33,7 +33,7 @@ endif if ENABLE_ZMQ qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) endif -qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) \ +qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) \ $(LIBMEMENV) $(BOOST_LIBS) $(QT_DBUS_LIBS) $(QT_TEST_LIBS) $(QT_LIBS) \ $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) $(LIBZCASH_LIBS) qt_test_test_bitcoin_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 56be626d..4defc09e 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -98,7 +98,7 @@ endif test_test_bitcoin_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES) test_test_bitcoin_CPPFLAGS = -fopenmp $(BITCOIN_INCLUDES) -I$(builddir)/test/ $(TESTDEFS) -test_test_bitcoin_LDADD = $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \ +test_test_bitcoin_LDADD = $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \ $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) if ENABLE_WALLET test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET) @@ -131,6 +131,7 @@ check-local: @echo "Running test/bitcoin-util-test.py..." $(AM_V_at)srcdir=$(srcdir) PYTHONPATH=$(builddir)/test $(srcdir)/test/bitcoin-util-test.py $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C secp256k1 check + $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C univalue check %.json.h: %.json @$(MKDIR_P) $(@D) diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index d589a5a7..6e54cd97 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -12,7 +12,7 @@ #include -#include "univalue/univalue.h" +#include using namespace std; diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index b36a01b7..0ba7e9a4 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -11,7 +11,7 @@ #include "primitives/transaction.h" #include "script/script.h" #include "script/sign.h" -#include "univalue/univalue.h" +#include #include "util.h" #include "utilmoneystr.h" #include "utilstrencodings.h" diff --git a/src/core_read.cpp b/src/core_read.cpp index e064955f..1b3e9025 100644 --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -9,7 +9,7 @@ #include "script/script.h" #include "serialize.h" #include "streams.h" -#include "univalue/univalue.h" +#include #include "util.h" #include "utilstrencodings.h" #include "version.h" diff --git a/src/core_write.cpp b/src/core_write.cpp index c3babec2..26eeba65 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -10,7 +10,7 @@ #include "script/standard.h" #include "serialize.h" #include "streams.h" -#include "univalue/univalue.h" +#include #include "util.h" #include "utilmoneystr.h" #include "utilstrencodings.h" diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index feb28264..ca3af257 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -18,7 +18,7 @@ #include -#include "univalue/univalue.h" +#include #ifdef ENABLE_WALLET #include diff --git a/src/rest.cpp b/src/rest.cpp index 1fce7dfc..82155bab 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -16,7 +16,7 @@ #include #include -#include "univalue/univalue.h" +#include using namespace std; diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index fef578cc..365ae358 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -13,7 +13,7 @@ #include -#include "univalue/univalue.h" +#include using namespace std; diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index f0c6611a..fe442b81 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -11,7 +11,7 @@ #include #include -#include "univalue/univalue.h" +#include using namespace std; diff --git a/src/rpcclient.h b/src/rpcclient.h index d68b4ed6..8937a56f 100644 --- a/src/rpcclient.h +++ b/src/rpcclient.h @@ -6,7 +6,7 @@ #ifndef BITCOIN_RPCCLIENT_H #define BITCOIN_RPCCLIENT_H -#include "univalue/univalue.h" +#include UniValue RPCConvertValues(const std::string& strMethod, const std::vector& strParams); /** Non-RFC4627 JSON parser, accepts internal values (such as numbers, true, false, null) diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp index 8f63b752..dd622444 100644 --- a/src/rpcmining.cpp +++ b/src/rpcmining.cpp @@ -28,7 +28,7 @@ #include -#include "univalue/univalue.h" +#include using namespace std; diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index ef480034..38610903 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -20,7 +20,8 @@ #include #include -#include "univalue/univalue.h" + +#include #include "zcash/Address.hpp" diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp index e8eca24f..522edc74 100644 --- a/src/rpcnet.cpp +++ b/src/rpcnet.cpp @@ -16,7 +16,7 @@ #include -#include "univalue/univalue.h" +#include using namespace std; diff --git a/src/rpcprotocol.h b/src/rpcprotocol.h index b6e61815..852a3aef 100644 --- a/src/rpcprotocol.h +++ b/src/rpcprotocol.h @@ -16,7 +16,7 @@ #include #include -#include "univalue/univalue.h" +#include //! HTTP status codes enum HTTPStatusCode diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index a23c1d8e..5ff03516 100644 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -26,7 +26,7 @@ #include -#include "univalue/univalue.h" +#include using namespace std; diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 6fe41193..89250b54 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -31,7 +31,7 @@ #include #include -#include "univalue/univalue.h" +#include using namespace boost::asio; using namespace RPCServer; diff --git a/src/rpcserver.h b/src/rpcserver.h index 607cff1c..6ee17da6 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -18,7 +18,7 @@ #include -#include "univalue/univalue.h" +#include class AsyncRPCQueue; class CRPCCommand; diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp index bd754caf..361d8b8e 100644 --- a/src/test/base58_tests.cpp +++ b/src/test/base58_tests.cpp @@ -18,7 +18,7 @@ #include #include -#include "univalue/univalue.h" +#include extern UniValue read_json(const std::string& jsondata); diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 9037d3f1..1dfb3e22 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -13,7 +13,7 @@ #include #include -#include "univalue/univalue.h" +#include using namespace std; diff --git a/src/test/rpc_wallet_tests.cpp b/src/test/rpc_wallet_tests.cpp index f3d1ce0e..1dd74eb0 100644 --- a/src/test/rpc_wallet_tests.cpp +++ b/src/test/rpc_wallet_tests.cpp @@ -31,7 +31,7 @@ #include #include -#include "univalue/univalue.h" +#include using namespace std; diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index dfb4c2f6..a1e3dbdc 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -27,7 +27,7 @@ #include #include -#include "univalue/univalue.h" +#include using namespace std; diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp index ce554ee3..989ed742 100644 --- a/src/test/sighash_tests.cpp +++ b/src/test/sighash_tests.cpp @@ -18,7 +18,7 @@ #include -#include "univalue/univalue.h" +#include extern UniValue read_json(const std::string& jsondata); diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index d2e9a338..06588361 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -27,7 +27,7 @@ #include #include -#include "univalue/univalue.h" +#include #include "zcash/Note.hpp" #include "zcash/Address.hpp" diff --git a/src/test/univalue_tests.cpp b/src/test/univalue_tests.cpp index de84faca..6eb8d1ab 100644 --- a/src/test/univalue_tests.cpp +++ b/src/test/univalue_tests.cpp @@ -6,7 +6,7 @@ #include #include #include -#include "univalue/univalue.h" +#include #include "test/test_bitcoin.h" #include diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 7f9c89da..7ae68b99 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -19,7 +19,7 @@ #include #include -#include "univalue/univalue.h" +#include using namespace std; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 5e1efe2d..562b60b6 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -31,7 +31,7 @@ #include -#include "univalue/univalue.h" +#include #include From fbc98d77201c1f985c2992bc4f52960b84460264 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Thu, 1 Oct 2015 13:01:58 +0200 Subject: [PATCH 04/16] remove $(@F) and subdirs from univalue make --- src/Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 1cd85e2f..aca0d64e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -37,8 +37,8 @@ LIBZCASH=libzcash.a $(LIBSECP256K1): $(wildcard secp256k1/src/*) $(wildcard secp256k1/include/*) $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F) -$(LIBUNIVALUE): $(wildcard univalue/lib/*) $(wildcard univalue/include/*) - $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F) +$(LIBUNIVALUE): $(wildcard univalue/lib/*) + $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C univalue/ # Make is not made aware of per-object dependencies to avoid limiting building parallelization # But to build the less dependent modules first, we manually select their order here: From c66dff3dc70862dd4bac78ca2bcf8f3ff004bb8a Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Thu, 4 Jun 2015 14:22:26 +0200 Subject: [PATCH 05/16] Don't go through double in AmountFromValue and ValueFromAmount My prime gripe with JSON spirit was that monetary values still had to be converted from and to floating point which can cause deviations (see #3759 and https://bitcoin.stackexchange.com/questions/22716/bitcoind-sendfrom-round-amount-error). As UniValue stores internal values as strings, this is no longer necessary. This avoids risky double-to-integer and integer-to-double conversions completely, and results in more elegant code to boot. --- src/rpcserver.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 89250b54..c31d8609 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -11,6 +11,7 @@ #include "sync.h" #include "ui_interface.h" #include "util.h" +#include "utilmoneystr.h" #include "utilstrencodings.h" #ifdef ENABLE_WALLET #include "wallet/wallet.h" @@ -121,25 +122,21 @@ void RPCTypeCheckObj(const UniValue& o, } } -static inline int64_t roundint64(double d) -{ - return (int64_t)(d > 0 ? d + 0.5 : d - 0.5); -} - CAmount AmountFromValue(const UniValue& value) { - double dAmount = value.get_real(); - if (dAmount <= 0.0 || dAmount > 21000000.0) + if (!value.isReal() && !value.isNum()) + throw JSONRPCError(RPC_TYPE_ERROR, "Amount is not a number"); + CAmount amount; + if (!ParseMoney(value.getValStr(), amount)) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); - CAmount nAmount = roundint64(dAmount * COIN); - if (!MoneyRange(nAmount)) - throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); - return nAmount; + if (!MoneyRange(amount)) + throw JSONRPCError(RPC_TYPE_ERROR, "Amount out of range"); + return amount; } UniValue ValueFromAmount(const CAmount& amount) { - return (double)amount / (double)COIN; + return UniValue(UniValue::VREAL, FormatMoney(amount, false)); } uint256 ParseHashV(const UniValue& v, string strName) From a5beb3a51b66bf8b316558dc486e41c5faf370cf Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Thu, 4 Jun 2015 14:43:02 +0200 Subject: [PATCH 06/16] Get rid of fPlus argument to FormatMoney It's never used with any other value than false, the default. --- src/rpcserver.cpp | 2 +- src/test/util_tests.cpp | 42 ++++++++++++++++++++--------------------- src/utilmoneystr.cpp | 4 +--- src/utilmoneystr.h | 2 +- 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index c31d8609..f7aa3766 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -136,7 +136,7 @@ CAmount AmountFromValue(const UniValue& value) UniValue ValueFromAmount(const CAmount& amount) { - return UniValue(UniValue::VREAL, FormatMoney(amount, false)); + return UniValue(UniValue::VREAL, FormatMoney(amount)); } uint256 ParseHashV(const UniValue& v, string strName) diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 2224a119..a6c54962 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -146,29 +146,27 @@ BOOST_AUTO_TEST_CASE(util_GetArg) BOOST_AUTO_TEST_CASE(util_FormatMoney) { - BOOST_CHECK_EQUAL(FormatMoney(0, false), "0.00"); - BOOST_CHECK_EQUAL(FormatMoney((COIN/10000)*123456789, false), "12345.6789"); - BOOST_CHECK_EQUAL(FormatMoney(COIN, true), "+1.00"); - BOOST_CHECK_EQUAL(FormatMoney(-COIN, false), "-1.00"); - BOOST_CHECK_EQUAL(FormatMoney(-COIN, true), "-1.00"); + BOOST_CHECK_EQUAL(FormatMoney(0), "0.00"); + BOOST_CHECK_EQUAL(FormatMoney((COIN/10000)*123456789), "12345.6789"); + BOOST_CHECK_EQUAL(FormatMoney(-COIN), "-1.00"); - BOOST_CHECK_EQUAL(FormatMoney(COIN*100000000, false), "100000000.00"); - BOOST_CHECK_EQUAL(FormatMoney(COIN*10000000, false), "10000000.00"); - BOOST_CHECK_EQUAL(FormatMoney(COIN*1000000, false), "1000000.00"); - BOOST_CHECK_EQUAL(FormatMoney(COIN*100000, false), "100000.00"); - BOOST_CHECK_EQUAL(FormatMoney(COIN*10000, false), "10000.00"); - BOOST_CHECK_EQUAL(FormatMoney(COIN*1000, false), "1000.00"); - BOOST_CHECK_EQUAL(FormatMoney(COIN*100, false), "100.00"); - BOOST_CHECK_EQUAL(FormatMoney(COIN*10, false), "10.00"); - BOOST_CHECK_EQUAL(FormatMoney(COIN, false), "1.00"); - BOOST_CHECK_EQUAL(FormatMoney(COIN/10, false), "0.10"); - BOOST_CHECK_EQUAL(FormatMoney(COIN/100, false), "0.01"); - BOOST_CHECK_EQUAL(FormatMoney(COIN/1000, false), "0.001"); - BOOST_CHECK_EQUAL(FormatMoney(COIN/10000, false), "0.0001"); - BOOST_CHECK_EQUAL(FormatMoney(COIN/100000, false), "0.00001"); - BOOST_CHECK_EQUAL(FormatMoney(COIN/1000000, false), "0.000001"); - BOOST_CHECK_EQUAL(FormatMoney(COIN/10000000, false), "0.0000001"); - BOOST_CHECK_EQUAL(FormatMoney(COIN/100000000, false), "0.00000001"); + BOOST_CHECK_EQUAL(FormatMoney(COIN*100000000), "100000000.00"); + BOOST_CHECK_EQUAL(FormatMoney(COIN*10000000), "10000000.00"); + BOOST_CHECK_EQUAL(FormatMoney(COIN*1000000), "1000000.00"); + BOOST_CHECK_EQUAL(FormatMoney(COIN*100000), "100000.00"); + BOOST_CHECK_EQUAL(FormatMoney(COIN*10000), "10000.00"); + BOOST_CHECK_EQUAL(FormatMoney(COIN*1000), "1000.00"); + BOOST_CHECK_EQUAL(FormatMoney(COIN*100), "100.00"); + BOOST_CHECK_EQUAL(FormatMoney(COIN*10), "10.00"); + BOOST_CHECK_EQUAL(FormatMoney(COIN), "1.00"); + BOOST_CHECK_EQUAL(FormatMoney(COIN/10), "0.10"); + BOOST_CHECK_EQUAL(FormatMoney(COIN/100), "0.01"); + BOOST_CHECK_EQUAL(FormatMoney(COIN/1000), "0.001"); + BOOST_CHECK_EQUAL(FormatMoney(COIN/10000), "0.0001"); + BOOST_CHECK_EQUAL(FormatMoney(COIN/100000), "0.00001"); + BOOST_CHECK_EQUAL(FormatMoney(COIN/1000000), "0.000001"); + BOOST_CHECK_EQUAL(FormatMoney(COIN/10000000), "0.0000001"); + BOOST_CHECK_EQUAL(FormatMoney(COIN/100000000), "0.00000001"); } BOOST_AUTO_TEST_CASE(util_ParseMoney) diff --git a/src/utilmoneystr.cpp b/src/utilmoneystr.cpp index 2fbc0488..0f320343 100644 --- a/src/utilmoneystr.cpp +++ b/src/utilmoneystr.cpp @@ -11,7 +11,7 @@ using namespace std; -string FormatMoney(const CAmount& n, bool fPlus) +std::string FormatMoney(const CAmount& n) { // Note: not using straight sprintf here because we do NOT want // localized number formatting. @@ -29,8 +29,6 @@ string FormatMoney(const CAmount& n, bool fPlus) if (n < 0) str.insert((unsigned int)0, 1, '-'); - else if (fPlus && n > 0) - str.insert((unsigned int)0, 1, '+'); return str; } diff --git a/src/utilmoneystr.h b/src/utilmoneystr.h index cd9b0481..99c3ba83 100644 --- a/src/utilmoneystr.h +++ b/src/utilmoneystr.h @@ -14,7 +14,7 @@ #include "amount.h" -std::string FormatMoney(const CAmount& n, bool fPlus=false); +std::string FormatMoney(const CAmount& n); bool ParseMoney(const std::string& str, CAmount& nRet); bool ParseMoney(const char* pszIn, CAmount& nRet); From e76a3849c6cdd390d2577e9e21436466fd800dda Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Sat, 6 Jun 2015 09:41:12 +0200 Subject: [PATCH 07/16] Changes necessary now that zero values accepted in AmountFromValue - Add an accept test for zero amounts, and a reject test for negative amounts - Remove ugly hack in `settxfee` that is no longer necessary - Do explicit zero checks in wallet RPC functions - Don't add a check for zero amounts in `createrawtransaction` - this could be seen as a feature --- src/test/rpc_tests.cpp | 3 +++ src/wallet/rpcwallet.cpp | 13 +++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 1dfb3e22..2c5a96c1 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -137,6 +137,9 @@ static UniValue ValueFromString(const std::string &str) BOOST_AUTO_TEST_CASE(rpc_parse_monetary_values) { + BOOST_CHECK_THROW(AmountFromValue(ValueFromString("-0.00000001")), UniValue); + BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0")), 0LL); + BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.00000000")), 0LL); BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.00000001")), 1LL); BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.17622195")), 17622195LL); BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.5")), 50000000LL); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 562b60b6..a0436373 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -433,6 +433,8 @@ UniValue sendtoaddress(const UniValue& params, bool fHelp) // Amount CAmount nAmount = AmountFromValue(params[1]); + if (nAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); // Wallet comments CWalletTx wtx; @@ -824,6 +826,8 @@ UniValue movecmd(const UniValue& params, bool fHelp) string strFrom = AccountFromValue(params[0]); string strTo = AccountFromValue(params[1]); CAmount nAmount = AmountFromValue(params[2]); + if (nAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); if (params.size() > 3) // unused parameter, used to be nMinDepth, keep type-checking it though (void)params[3].get_int(); @@ -903,6 +907,8 @@ UniValue sendfrom(const UniValue& params, bool fHelp) if (!address.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Zcash address"); CAmount nAmount = AmountFromValue(params[2]); + if (nAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); int nMinDepth = 1; if (params.size() > 3) nMinDepth = params[3].get_int(); @@ -1002,6 +1008,8 @@ UniValue sendmany(const UniValue& params, bool fHelp) CScript scriptPubKey = GetScriptForDestination(address.Get()); CAmount nAmount = AmountFromValue(sendTo[name_]); + if (nAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); totalAmount += nAmount; bool fSubtractFeeFromAmount = false; @@ -2227,9 +2235,7 @@ UniValue settxfee(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); // Amount - CAmount nAmount = 0; - if (params[0].get_real() != 0.0) - nAmount = AmountFromValue(params[0]); // rejects 0.0 amounts + CAmount nAmount = AmountFromValue(params[0]); payTxFee = CFeeRate(nAmount, 1000); return true; @@ -3471,4 +3477,3 @@ UniValue z_listoperationids(const UniValue& params, bool fHelp) return ret; } - From fed500e2dd571406b9420f7a26a5db6dee801806 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Mon, 6 Jul 2015 10:49:24 +0200 Subject: [PATCH 08/16] rpc: Accept scientific notation for monetary amounts in JSON Add a function `ParseFixedPoint` that parses numbers according to the JSON number specification and returns a 64-bit integer. Then this in `AmountFromValue`, rather than `ParseMoney`. Also add lots of tests (thanks to @jonasschnelli for some of them). Fixes issue #6297. --- src/rpcserver.cpp | 2 +- src/test/rpc_tests.cpp | 21 +++++++ src/test/util_tests.cpp | 66 +++++++++++++++++++++ src/utilstrencodings.cpp | 120 +++++++++++++++++++++++++++++++++++++++ src/utilstrencodings.h | 7 +++ 5 files changed, 215 insertions(+), 1 deletion(-) diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index f7aa3766..91b4848f 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -127,7 +127,7 @@ CAmount AmountFromValue(const UniValue& value) if (!value.isReal() && !value.isNum()) throw JSONRPCError(RPC_TYPE_ERROR, "Amount is not a number"); CAmount amount; - if (!ParseMoney(value.getValStr(), amount)) + if (!ParseFixedPoint(value.getValStr(), 8, &amount)) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); if (!MoneyRange(amount)) throw JSONRPCError(RPC_TYPE_ERROR, "Amount out of range"); diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 2c5a96c1..64190270 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -148,6 +148,24 @@ BOOST_AUTO_TEST_CASE(rpc_parse_monetary_values) BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("1.00000000")), 100000000LL); BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("20999999.9999999")), 2099999999999990LL); BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("20999999.99999999")), 2099999999999999LL); + + BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("1e-8")), COIN/100000000); + BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.1e-7")), COIN/100000000); + BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.01e-6")), COIN/100000000); + BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.0000000000000000000000000000000000000000000000000000000000000000000000000001e+68")), COIN/100000000); + BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("10000000000000000000000000000000000000000000000000000000000000000e-64")), COIN); + BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000e64")), COIN); + + BOOST_CHECK_THROW(AmountFromValue(ValueFromString("1e-9")), UniValue); //should fail + BOOST_CHECK_THROW(AmountFromValue(ValueFromString("0.000000019")), UniValue); //should fail + BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.00000001000000")), 1LL); //should pass, cut trailing 0 + BOOST_CHECK_THROW(AmountFromValue(ValueFromString("19e-9")), UniValue); //should fail + BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.19e-6")), 19); //should pass, leading 0 is present + + BOOST_CHECK_THROW(AmountFromValue(ValueFromString("92233720368.54775808")), UniValue); //overflow error + BOOST_CHECK_THROW(AmountFromValue(ValueFromString("1e+11")), UniValue); //overflow error + BOOST_CHECK_THROW(AmountFromValue(ValueFromString("1e11")), UniValue); //overflow error signless + BOOST_CHECK_THROW(AmountFromValue(ValueFromString("93e+9")), UniValue); //overflow error } BOOST_AUTO_TEST_CASE(json_parse_errors) @@ -157,6 +175,9 @@ BOOST_AUTO_TEST_CASE(json_parse_errors) // Valid, with leading or trailing whitespace BOOST_CHECK_EQUAL(ParseNonRFCJSONValue(" 1.0").get_real(), 1.0); BOOST_CHECK_EQUAL(ParseNonRFCJSONValue("1.0 ").get_real(), 1.0); + + BOOST_CHECK_THROW(AmountFromValue(ParseNonRFCJSONValue(".19e-6")), std::runtime_error); //should fail, missing leading 0, therefore invalid JSON + BOOST_CHECK_EQUAL(AmountFromValue(ParseNonRFCJSONValue("0.00000000000000000000000000000000000001e+30 ")), 1); // Invalid, initial garbage BOOST_CHECK_THROW(ParseNonRFCJSONValue("[1.0"), std::runtime_error); BOOST_CHECK_THROW(ParseNonRFCJSONValue("a1.0"), std::runtime_error); diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index a6c54962..df8f8f43 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -426,4 +426,70 @@ BOOST_AUTO_TEST_CASE(test_FormatSubVersion) BOOST_CHECK_EQUAL(FormatSubVersion("Test", 99900, comments2), std::string("/Test:0.9.99-beta1(comment1; comment2)/")); BOOST_CHECK_EQUAL(FormatSubVersion("Test", 99950, comments2), std::string("/Test:0.9.99(comment1; comment2)/")); } + +BOOST_AUTO_TEST_CASE(test_ParseFixedPoint) +{ + int64_t amount = 0; + BOOST_CHECK(ParseFixedPoint("0", 8, &amount)); + BOOST_CHECK_EQUAL(amount, 0LL); + BOOST_CHECK(ParseFixedPoint("1", 8, &amount)); + BOOST_CHECK_EQUAL(amount, 100000000LL); + BOOST_CHECK(ParseFixedPoint("0.0", 8, &amount)); + BOOST_CHECK_EQUAL(amount, 0LL); + BOOST_CHECK(ParseFixedPoint("-0.1", 8, &amount)); + BOOST_CHECK_EQUAL(amount, -10000000LL); + BOOST_CHECK(ParseFixedPoint("1.1", 8, &amount)); + BOOST_CHECK_EQUAL(amount, 110000000LL); + BOOST_CHECK(ParseFixedPoint("1.10000000000000000", 8, &amount)); + BOOST_CHECK_EQUAL(amount, 110000000LL); + BOOST_CHECK(ParseFixedPoint("1.1e1", 8, &amount)); + BOOST_CHECK_EQUAL(amount, 1100000000LL); + BOOST_CHECK(ParseFixedPoint("1.1e-1", 8, &amount)); + BOOST_CHECK_EQUAL(amount, 11000000LL); + BOOST_CHECK(ParseFixedPoint("1000", 8, &amount)); + BOOST_CHECK_EQUAL(amount, 100000000000LL); + BOOST_CHECK(ParseFixedPoint("-1000", 8, &amount)); + BOOST_CHECK_EQUAL(amount, -100000000000LL); + BOOST_CHECK(ParseFixedPoint("0.00000001", 8, &amount)); + BOOST_CHECK_EQUAL(amount, 1LL); + BOOST_CHECK(ParseFixedPoint("0.0000000100000000", 8, &amount)); + BOOST_CHECK_EQUAL(amount, 1LL); + BOOST_CHECK(ParseFixedPoint("-0.00000001", 8, &amount)); + BOOST_CHECK_EQUAL(amount, -1LL); + BOOST_CHECK(ParseFixedPoint("1000000000.00000001", 8, &amount)); + BOOST_CHECK_EQUAL(amount, 100000000000000001LL); + BOOST_CHECK(ParseFixedPoint("9999999999.99999999", 8, &amount)); + BOOST_CHECK_EQUAL(amount, 999999999999999999LL); + BOOST_CHECK(ParseFixedPoint("-9999999999.99999999", 8, &amount)); + BOOST_CHECK_EQUAL(amount, -999999999999999999LL); + + BOOST_CHECK(!ParseFixedPoint("", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("-", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("a-1000", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("-a1000", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("-1000a", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("-01000", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("00.1", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint(".1", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("--0.1", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("0.000000001", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("-0.000000001", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("0.00000001000000001", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("-10000000000.00000000", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("10000000000.00000000", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("-10000000000.00000001", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("10000000000.00000001", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("-10000000000.00000009", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("10000000000.00000009", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("-99999999999.99999999", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("99999909999.09999999", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("92233720368.54775807", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("92233720368.54775808", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("-92233720368.54775808", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("-92233720368.54775809", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("1.1e", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("1.1e-", 8, &amount)); + BOOST_CHECK(!ParseFixedPoint("1.", 8, &amount)); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/utilstrencodings.cpp b/src/utilstrencodings.cpp index 81f9e2f9..14482c99 100644 --- a/src/utilstrencodings.cpp +++ b/src/utilstrencodings.cpp @@ -554,3 +554,123 @@ int atoi(const std::string& str) { return atoi(str.c_str()); } + +/** Upper bound for mantissa. + * 10^18-1 is the largest arbitrary decimal that will fit in a signed 64-bit integer. + * Larger integers cannot consist of arbitrary combinations of 0-9: + * + * 999999999999999999 1^18-1 + * 9223372036854775807 (1<<63)-1 (max int64_t) + * 9999999999999999999 1^19-1 (would overflow) + */ +static const int64_t UPPER_BOUND = 1000000000000000000LL - 1LL; + +/** Helper function for ParseFixedPoint */ +static inline bool ProcessMantissaDigit(char ch, int64_t &mantissa, int &mantissa_tzeros) +{ + if(ch == '0') + ++mantissa_tzeros; + else { + for (int i=0; i<=mantissa_tzeros; ++i) { + if (mantissa > (UPPER_BOUND / 10LL)) + return false; /* overflow */ + mantissa *= 10; + } + mantissa += ch - '0'; + mantissa_tzeros = 0; + } + return true; +} + +bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out) +{ + int64_t mantissa = 0; + int64_t exponent = 0; + int mantissa_tzeros = 0; + bool mantissa_sign = false; + bool exponent_sign = false; + int ptr = 0; + int end = val.size(); + int point_ofs = 0; + + if (ptr < end && val[ptr] == '-') { + mantissa_sign = true; + ++ptr; + } + if (ptr < end) + { + if (val[ptr] == '0') { + /* pass single 0 */ + ++ptr; + } else if (val[ptr] >= '1' && val[ptr] <= '9') { + while (ptr < end && val[ptr] >= '0' && val[ptr] <= '9') { + if (!ProcessMantissaDigit(val[ptr], mantissa, mantissa_tzeros)) + return false; /* overflow */ + ++ptr; + } + } else return false; /* missing expected digit */ + } else return false; /* empty string or loose '-' */ + if (ptr < end && val[ptr] == '.') + { + ++ptr; + if (ptr < end && val[ptr] >= '0' && val[ptr] <= '9') + { + while (ptr < end && val[ptr] >= '0' && val[ptr] <= '9') { + if (!ProcessMantissaDigit(val[ptr], mantissa, mantissa_tzeros)) + return false; /* overflow */ + ++ptr; + ++point_ofs; + } + } else return false; /* missing expected digit */ + } + if (ptr < end && (val[ptr] == 'e' || val[ptr] == 'E')) + { + ++ptr; + if (ptr < end && val[ptr] == '+') + ++ptr; + else if (ptr < end && val[ptr] == '-') { + exponent_sign = true; + ++ptr; + } + if (ptr < end && val[ptr] >= '0' && val[ptr] <= '9') { + while (ptr < end && val[ptr] >= '0' && val[ptr] <= '9') { + if (exponent > (UPPER_BOUND / 10LL)) + return false; /* overflow */ + exponent = exponent * 10 + val[ptr] - '0'; + ++ptr; + } + } else return false; /* missing expected digit */ + } + if (ptr != end) + return false; /* trailing garbage */ + + /* finalize exponent */ + if (exponent_sign) + exponent = -exponent; + exponent = exponent - point_ofs + mantissa_tzeros; + + /* finalize mantissa */ + if (mantissa_sign) + mantissa = -mantissa; + + /* convert to one 64-bit fixed-point value */ + exponent += decimals; + if (exponent < 0) + return false; /* cannot represent values smaller than 10^-decimals */ + if (exponent >= 18) + return false; /* cannot represent values larger than or equal to 10^(18-decimals) */ + + for (int i=0; i < exponent; ++i) { + if (mantissa > (UPPER_BOUND / 10LL) || mantissa < -(UPPER_BOUND / 10LL)) + return false; /* overflow */ + mantissa *= 10; + } + if (mantissa > UPPER_BOUND || mantissa < -UPPER_BOUND) + return false; /* overflow */ + + if (amount_out) + *amount_out = mantissa; + + return true; +} + diff --git a/src/utilstrencodings.h b/src/utilstrencodings.h index a6184d8f..08d33772 100644 --- a/src/utilstrencodings.h +++ b/src/utilstrencodings.h @@ -110,4 +110,11 @@ bool TimingResistantEqual(const T& a, const T& b) return accumulator == 0; } +/** Parse number as fixed point according to JSON number syntax. + * See http://json.org/number.gif + * @returns true on success, false on error. + * @note The result must be in the range (-10^18,10^18), otherwise an overflow error will trigger. + */ +bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out); + #endif // BITCOIN_UTILSTRENCODINGS_H From d5bf1afae92b77974ad5ef4397ec8b91c6dcb03d Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Sat, 18 Jul 2015 07:42:23 +0200 Subject: [PATCH 09/16] rpc: Make ValueFromAmount always return 8 decimals This is the format that was always returned to JSON clients. The difference was not noticed before, because VREAL values are post-processed by univalue. By implementing the functionality directly it breaks the dependency of rpcserver on utilmoneystr. FormatMoney is now only used for debugging purposes. To test, port over the formatting tests from util_tests.cpp to rpc_tests.cpp. --- src/rpcserver.cpp | 8 ++++++-- src/test/rpc_tests.cpp | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 91b4848f..16f7c0d6 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -11,7 +11,6 @@ #include "sync.h" #include "ui_interface.h" #include "util.h" -#include "utilmoneystr.h" #include "utilstrencodings.h" #ifdef ENABLE_WALLET #include "wallet/wallet.h" @@ -136,7 +135,12 @@ CAmount AmountFromValue(const UniValue& value) UniValue ValueFromAmount(const CAmount& amount) { - return UniValue(UniValue::VREAL, FormatMoney(amount)); + bool sign = amount < 0; + int64_t n_abs = (sign ? -amount : amount); + int64_t quotient = n_abs / COIN; + int64_t remainder = n_abs % COIN; + return UniValue(UniValue::VNUM, + strprintf("%s%d.%08d", sign ? "-" : "", quotient, remainder)); } uint256 ParseHashV(const UniValue& v, string strName) diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 64190270..758c02f6 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -126,6 +126,29 @@ BOOST_AUTO_TEST_CASE(rpc_format_monetary_values) BOOST_CHECK(ValueFromAmount(100000000LL).write() == "1.00000000"); BOOST_CHECK(ValueFromAmount(2099999999999990LL).write() == "20999999.99999990"); BOOST_CHECK(ValueFromAmount(2099999999999999LL).write() == "20999999.99999999"); + + BOOST_CHECK_EQUAL(ValueFromAmount(0).write(), "0.00000000"); + BOOST_CHECK_EQUAL(ValueFromAmount((COIN/10000)*123456789).write(), "12345.67890000"); + BOOST_CHECK_EQUAL(ValueFromAmount(-COIN).write(), "-1.00000000"); + BOOST_CHECK_EQUAL(ValueFromAmount(-COIN/10).write(), "-0.10000000"); + + BOOST_CHECK_EQUAL(ValueFromAmount(COIN*100000000).write(), "100000000.00000000"); + BOOST_CHECK_EQUAL(ValueFromAmount(COIN*10000000).write(), "10000000.00000000"); + BOOST_CHECK_EQUAL(ValueFromAmount(COIN*1000000).write(), "1000000.00000000"); + BOOST_CHECK_EQUAL(ValueFromAmount(COIN*100000).write(), "100000.00000000"); + BOOST_CHECK_EQUAL(ValueFromAmount(COIN*10000).write(), "10000.00000000"); + BOOST_CHECK_EQUAL(ValueFromAmount(COIN*1000).write(), "1000.00000000"); + BOOST_CHECK_EQUAL(ValueFromAmount(COIN*100).write(), "100.00000000"); + BOOST_CHECK_EQUAL(ValueFromAmount(COIN*10).write(), "10.00000000"); + BOOST_CHECK_EQUAL(ValueFromAmount(COIN).write(), "1.00000000"); + BOOST_CHECK_EQUAL(ValueFromAmount(COIN/10).write(), "0.10000000"); + BOOST_CHECK_EQUAL(ValueFromAmount(COIN/100).write(), "0.01000000"); + BOOST_CHECK_EQUAL(ValueFromAmount(COIN/1000).write(), "0.00100000"); + BOOST_CHECK_EQUAL(ValueFromAmount(COIN/10000).write(), "0.00010000"); + BOOST_CHECK_EQUAL(ValueFromAmount(COIN/100000).write(), "0.00001000"); + BOOST_CHECK_EQUAL(ValueFromAmount(COIN/1000000).write(), "0.00000100"); + BOOST_CHECK_EQUAL(ValueFromAmount(COIN/10000000).write(), "0.00000010"); + BOOST_CHECK_EQUAL(ValueFromAmount(COIN/100000000).write(), "0.00000001"); } static UniValue ValueFromString(const std::string &str) From 2aee4619304adb9cecf4a9a4b04d83e0fc4d9d8d Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Sat, 18 Jul 2015 07:44:19 +0200 Subject: [PATCH 10/16] univalue: Avoid unnecessary roundtrip through double for numbers JSON makes no distinction between numbers and reals, and our code doesn't need to do so either. This removes VREAL, as well as its specific post-processing in `UniValue::write`. Non-monetary amounts do not need to be forcibly formatted with 8 decimals, so the extra roundtrip was unnecessary (and potentially loses precision). Zcash: cherry-picked from commit 7650449a6777710cf818d41862626164da0cd412 Dropped changes to qa/rpc-tests/rest.py pending addition of /rest/headers/ --- src/rpcserver.cpp | 2 +- src/test/univalue_tests.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 16f7c0d6..7414ec12 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -123,7 +123,7 @@ void RPCTypeCheckObj(const UniValue& o, CAmount AmountFromValue(const UniValue& value) { - if (!value.isReal() && !value.isNum()) + if (!value.isNum()) throw JSONRPCError(RPC_TYPE_ERROR, "Amount is not a number"); CAmount amount; if (!ParseFixedPoint(value.getValStr(), 8, &amount)) diff --git a/src/test/univalue_tests.cpp b/src/test/univalue_tests.cpp index 6eb8d1ab..feb8afa6 100644 --- a/src/test/univalue_tests.cpp +++ b/src/test/univalue_tests.cpp @@ -49,7 +49,7 @@ BOOST_AUTO_TEST_CASE(univalue_constructor) double vd = -7.21; UniValue v7(vd); - BOOST_CHECK(v7.isReal()); + BOOST_CHECK(v7.isNum()); BOOST_CHECK_EQUAL(v7.getValStr(), "-7.21"); string vs("yawn"); @@ -127,7 +127,7 @@ BOOST_AUTO_TEST_CASE(univalue_set) BOOST_CHECK_EQUAL(v.getValStr(), "zum"); BOOST_CHECK(v.setFloat(-1.01)); - BOOST_CHECK(v.isReal()); + BOOST_CHECK(v.isNum()); BOOST_CHECK_EQUAL(v.getValStr(), "-1.01"); BOOST_CHECK(v.setInt((int)1023)); @@ -272,7 +272,7 @@ BOOST_AUTO_TEST_CASE(univalue_object) objTypes["distance"] = UniValue::VNUM; objTypes["time"] = UniValue::VNUM; objTypes["calories"] = UniValue::VNUM; - objTypes["temperature"] = UniValue::VREAL; + objTypes["temperature"] = UniValue::VNUM; objTypes["cat1"] = UniValue::VNUM; objTypes["cat2"] = UniValue::VNUM; BOOST_CHECK(obj.checkObject(objTypes)); From a8c22cc4d1e3c55d2b2f16a72fe4130be54baaa4 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Sat, 18 Jul 2015 08:16:21 +0200 Subject: [PATCH 11/16] util: use locale-independent parsing in ParseDouble Use locale-indepent C++ based parsing instead of C's strtod, which checks for different input based on the user's locale. Fixes #6443. --- src/utilstrencodings.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/utilstrencodings.cpp b/src/utilstrencodings.cpp index 14482c99..142d5a4e 100644 --- a/src/utilstrencodings.cpp +++ b/src/utilstrencodings.cpp @@ -480,11 +480,12 @@ bool ParseDouble(const std::string& str, double *out) return false; if (str.size() >= 2 && str[0] == '0' && str[1] == 'x') // No hexadecimal floats allowed return false; - char *endp = NULL; - errno = 0; // strtod will not set errno if valid - double n = strtod(str.c_str(), &endp); - if(out) *out = n; - return endp && *endp == 0 && !errno; + std::istringstream text(str); + text.imbue(std::locale::classic()); + double result; + text >> result; + if(out) *out = result; + return text.eof() && !text.fail(); } std::string FormatParagraph(const std::string in, size_t width, size_t indent) From 1dc626314d5d0911ef3f6eeccd99e4825ab829cf Mon Sep 17 00:00:00 2001 From: Cory Fields Date: Thu, 8 Oct 2015 00:09:44 -0400 Subject: [PATCH 12/16] build: match upstream build change --- src/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile.am b/src/Makefile.am index aca0d64e..edf92862 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -31,7 +31,7 @@ LIBBITCOIN_UTIL=libbitcoin_util.a LIBBITCOIN_CRYPTO=crypto/libbitcoin_crypto.a LIBBITCOINQT=qt/libbitcoinqt.a LIBSECP256K1=secp256k1/libsecp256k1.la -LIBUNIVALUE=univalue/lib/libunivalue.la +LIBUNIVALUE=univalue/libunivalue.la LIBZCASH=libzcash.a $(LIBSECP256K1): $(wildcard secp256k1/src/*) $(wildcard secp256k1/include/*) From 2cc6bab2011ddecd107aa8775b9c0c0467a1e475 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 10 Feb 2017 00:28:29 +0000 Subject: [PATCH 13/16] Update UniValue includes in Zcash-specific code --- src/Makefile.gtest.include | 4 ++-- src/asyncrpcoperation.h | 2 +- src/gtest/json_test_vectors.h | 2 +- src/gtest/test_rpc.cpp | 2 +- src/rpcprotocol.cpp | 2 +- src/wallet/asyncrpcoperation_sendmany.h | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Makefile.gtest.include b/src/Makefile.gtest.include index d608bfcc..8db5aea4 100644 --- a/src/Makefile.gtest.include +++ b/src/Makefile.gtest.include @@ -40,9 +40,9 @@ zcash_gtest_SOURCES += \ wallet/gtest/test_wallet.cpp endif -zcash_gtest_CPPFLAGS = -DMULTICORE -fopenmp -DBINARY_OUTPUT -DCURVE_ALT_BN128 -DSTATIC +zcash_gtest_CPPFLAGS = -DMULTICORE -fopenmp -DBINARY_OUTPUT -DCURVE_ALT_BN128 -DSTATIC $(BITCOIN_INCLUDES) -zcash_gtest_LDADD = -lgtest -lgmock $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \ +zcash_gtest_LDADD = -lgtest -lgmock $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \ $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) if ENABLE_ZMQ zcash_gtest_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) diff --git a/src/asyncrpcoperation.h b/src/asyncrpcoperation.h index e095e57b..5475102a 100644 --- a/src/asyncrpcoperation.h +++ b/src/asyncrpcoperation.h @@ -15,7 +15,7 @@ #include #include -#include "univalue/univalue.h" +#include using namespace std; diff --git a/src/gtest/json_test_vectors.h b/src/gtest/json_test_vectors.h index 27c8757e..3639c912 100644 --- a/src/gtest/json_test_vectors.h +++ b/src/gtest/json_test_vectors.h @@ -5,7 +5,7 @@ #include "serialize.h" #include "streams.h" -#include "univalue/univalue.h" +#include UniValue read_json(const std::string& jsondata); diff --git a/src/gtest/test_rpc.cpp b/src/gtest/test_rpc.cpp index baebc7c6..a3fc201c 100644 --- a/src/gtest/test_rpc.cpp +++ b/src/gtest/test_rpc.cpp @@ -1,5 +1,5 @@ #include -#include "univalue/univalue.h" +#include #include "chain.h" #include "chainparams.h" diff --git a/src/rpcprotocol.cpp b/src/rpcprotocol.cpp index c0aaf2c2..684feb82 100644 --- a/src/rpcprotocol.cpp +++ b/src/rpcprotocol.cpp @@ -26,7 +26,7 @@ #include #include -#include "univalue/univalue.h" +#include using namespace std; diff --git a/src/wallet/asyncrpcoperation_sendmany.h b/src/wallet/asyncrpcoperation_sendmany.h index e870e603..6fac6116 100644 --- a/src/wallet/asyncrpcoperation_sendmany.h +++ b/src/wallet/asyncrpcoperation_sendmany.h @@ -16,7 +16,7 @@ #include #include -#include "univalue/univalue.h" +#include // Default transaction fee if caller does not specify one. #define ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE 10000 From c24109ecc3e4a11fe2f70e2df00ab5a5945c1053 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 10 Feb 2017 00:33:34 +0000 Subject: [PATCH 14/16] UniValue::getValues const reference --- src/wallet/rpcwallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index a0436373..f4b13025 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3183,7 +3183,7 @@ UniValue z_getoperationstatus_IMPL(const UniValue& params, bool fRemoveFinishedO std::set filter; if (params.size()==1) { UniValue ids = params[0].get_array(); - for (UniValue & v : ids.getValues()) { + for (const UniValue & v : ids.getValues()) { filter.insert(v.get_str()); } } From f54db399f2a1a16cf0cb7934abd6216ef17d5b8b Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 10 Feb 2017 01:39:34 +0000 Subject: [PATCH 15/16] Get rid of fPlus argument to FormatMoney in Zcash-specific code --- src/wallet/asyncrpcoperation_sendmany.cpp | 34 +++++++++++------------ src/wallet/rpcwallet.cpp | 6 ++-- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index e18dd440..de9744bb 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -277,12 +277,12 @@ bool AsyncRPCOperation_sendmany::main_impl() { } LogPrint((isfromtaddr_) ? "zrpc" : "zrpcunsafe", "%s: spending %s to send %s with fee %s\n", - getId(), FormatMoney(targetAmount, false), FormatMoney(sendAmount, false), FormatMoney(minersFee, false)); - LogPrint("zrpc", "%s: transparent input: %s (to choose from)\n", getId(), FormatMoney(t_inputs_total, false)); - LogPrint("zrpcunsafe", "%s: private input: %s (to choose from)\n", getId(), FormatMoney(z_inputs_total, false)); - LogPrint("zrpc", "%s: transparent output: %s\n", getId(), FormatMoney(t_outputs_total, false)); - LogPrint("zrpcunsafe", "%s: private output: %s\n", getId(), FormatMoney(z_outputs_total, false)); - LogPrint("zrpc", "%s: fee: %s\n", getId(), FormatMoney(minersFee, false)); + getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(minersFee)); + LogPrint("zrpc", "%s: transparent input: %s (to choose from)\n", getId(), FormatMoney(t_inputs_total)); + LogPrint("zrpcunsafe", "%s: private input: %s (to choose from)\n", getId(), FormatMoney(z_inputs_total)); + LogPrint("zrpc", "%s: transparent output: %s\n", getId(), FormatMoney(t_outputs_total)); + LogPrint("zrpcunsafe", "%s: private output: %s\n", getId(), FormatMoney(z_outputs_total)); + LogPrint("zrpc", "%s: fee: %s\n", getId(), FormatMoney(minersFee)); /** * SCENARIO #1 @@ -303,7 +303,7 @@ bool AsyncRPCOperation_sendmany::main_impl() { LogPrint("zrpc", "%s: transparent change in transaction output (amount=%s)\n", getId(), - FormatMoney(change, false) + FormatMoney(change) ); } @@ -379,7 +379,7 @@ bool AsyncRPCOperation_sendmany::main_impl() { add_taddr_change_output_to_tx(change); LogPrint("zrpc", "%s: transparent change in transaction output (amount=%s)\n", getId(), - FormatMoney(change, false) + FormatMoney(change) ); } } @@ -472,7 +472,7 @@ bool AsyncRPCOperation_sendmany::main_impl() { outPoint.hash.ToString().substr(0, 10), outPoint.js, int(outPoint.n), // uint8_t - FormatMoney(noteFunds, false), + FormatMoney(noteFunds), wtxHeight, wtxDepth ); @@ -498,7 +498,7 @@ bool AsyncRPCOperation_sendmany::main_impl() { LogPrint("zrpcunsafe", "%s: generating note for change (amount=%s)\n", getId(), - FormatMoney(jsChange, false) + FormatMoney(jsChange) ); } @@ -594,7 +594,7 @@ bool AsyncRPCOperation_sendmany::main_impl() { LogPrint("zrpcunsafe", "%s: spending change (amount=%s)\n", getId(), - FormatMoney(plaintext.value, false) + FormatMoney(plaintext.value) ); } catch (const std::exception& e) { @@ -644,7 +644,7 @@ bool AsyncRPCOperation_sendmany::main_impl() { jso.hash.ToString().substr(0, 10), jso.js, int(jso.n), // uint8_t - FormatMoney(noteFunds, false), + FormatMoney(noteFunds), wtxHeight, wtxDepth ); @@ -736,7 +736,7 @@ bool AsyncRPCOperation_sendmany::main_impl() { LogPrint("zrpcunsafe", "%s: generating note for change (amount=%s)\n", getId(), - FormatMoney(jsChange, false) + FormatMoney(jsChange) ); } @@ -879,7 +879,7 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() { entry.jsop.hash.ToString().substr(0, 10), entry.jsop.js, int(entry.jsop.n), // uint8_t - FormatMoney(entry.plaintext.value, false), + FormatMoney(entry.plaintext.value), HexStr(data).substr(0, 10) ); } @@ -955,9 +955,9 @@ UniValue AsyncRPCOperation_sendmany::perform_joinsplit( LogPrint("zrpcunsafe", "%s: creating joinsplit at index %d (vpub_old=%s, vpub_new=%s, in[0]=%s, in[1]=%s, out[0]=%s, out[1]=%s)\n", getId(), tx_.vjoinsplit.size(), - FormatMoney(info.vpub_old, false), FormatMoney(info.vpub_new, false), - FormatMoney(info.vjsin[0].note.value, false), FormatMoney(info.vjsin[1].note.value, false), - FormatMoney(info.vjsout[0].value, false), FormatMoney(info.vjsout[1].value, false) + FormatMoney(info.vpub_old), FormatMoney(info.vpub_new), + FormatMoney(info.vjsin[0].note.value), FormatMoney(info.vjsin[1].note.value), + FormatMoney(info.vjsout[0].value), FormatMoney(info.vjsout[1].value) ); // Generate the proof, this can take over a minute. diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index f4b13025..a23ce1a3 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3130,9 +3130,9 @@ UniValue z_gettotalbalance(const UniValue& params, bool fHelp) CAmount nPrivateBalance = getBalanceZaddr("", nMinDepth); CAmount nTotalBalance = nBalance + nPrivateBalance; UniValue result(UniValue::VOBJ); - result.push_back(Pair("transparent", FormatMoney(nBalance, false))); - result.push_back(Pair("private", FormatMoney(nPrivateBalance, false))); - result.push_back(Pair("total", FormatMoney(nTotalBalance, false))); + result.push_back(Pair("transparent", FormatMoney(nBalance))); + result.push_back(Pair("private", FormatMoney(nPrivateBalance))); + result.push_back(Pair("total", FormatMoney(nTotalBalance))); return result; } From 238fec642d06c93641f1b159e50ae7cfdffc929e Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 9 Feb 2017 23:31:50 -0800 Subject: [PATCH 16/16] Update test to check for updated error messages in AmountFromValue(). --- qa/rpc-tests/wallet_protectcoinbase.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qa/rpc-tests/wallet_protectcoinbase.py b/qa/rpc-tests/wallet_protectcoinbase.py index fc543a6e..7eefb8b9 100755 --- a/qa/rpc-tests/wallet_protectcoinbase.py +++ b/qa/rpc-tests/wallet_protectcoinbase.py @@ -242,14 +242,14 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): self.nodes[0].z_sendmany(myzaddr, recipients, 1, -1) except JSONRPCException,e: errorString = e.error['message'] - assert_equal("Invalid amount" in errorString, True) + assert_equal("Amount out of range" in errorString, True) # Send will fail because fee is larger than MAX_MONEY try: self.nodes[0].z_sendmany(myzaddr, recipients, 1, Decimal('21000000.00000001')) except JSONRPCException,e: errorString = e.error['message'] - assert_equal("Invalid amount" in errorString, True) + assert_equal("Amount out of range" in errorString, True) # Send will fail because fee is larger than sum of outputs try: