<commit_before>/*
 * Teng -- a general purpose templating engine.
 * Copyright (C) 2004  Seznam.cz, a.s.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Seznam.cz, a.s.
 * Naskove 1, Praha 5, 15000, Czech Republic
 * http://www.seznam.cz, mailto:teng@firma.seznam.cz
 *
 * $Id: tengcontenttype.cc,v 1.3 2005-01-02 16:32:16 vasek Exp $
 *
 * DESCRIPTION
 * Teng language descriptor -- implementation.
 *
 * AUTHORS
 * Vaclav Blazek <blazek@firma.seznam.cz>
 *
 * HISTORY
 * 2003-09-24  (vasek)
 *             Created.
 */

#include <string.h>
#include <utility>
#include <algorithm>
#include <ctype.h>

#include "tengcontenttype.h"

using namespace std;

using namespace Teng;

namespace {
    map<string, ContentType_t::Descriptor_t*> *descriptors = 0;
    vector<ContentType_t::Descriptor_t*> *descriptorIndex = 0;

    ContentType_t::Descriptor_t *unknown = 0;
}

ContentType_t::ContentType_t()
    : lineComment(), blockComment(), escapes(),
      unescaper()
{
    // set escape bitmap to all -1 (character not escaped)
    int *end = escapeBitmap + 256;
    int *i = escapeBitmap;
    while (i != end) *i++ = -1;
    // create empty automaton
    unescaper.push_back(pair<int, int>(0, 0));
}

int ContentType_t::addEscape(unsigned char c, const string &escape) {
    // if escape already present in bitmap make it error
    if (escapeBitmap[c] != -1) return -1;
    // add escape entry
    escapes.push_back(pair<unsigned char, string>(c, escape));
    // update entry in escape bitmap
    return escapeBitmap[c] = escapes.size() - 1;
}

string ContentType_t::escape(const string &src) const {
    // output string
    string dest;
    dest.reserve(src.length());

    // run through input string
    for (string::const_iterator isrc = src.begin();
         isrc != src.end(); ++isrc) {
        // find entry in bitmap
        int pos = escapeBitmap[static_cast<unsigned char>(*isrc)];
        // if position is negative pass source character to output
        if (pos < 0) dest.push_back(*isrc);
        // else append escape sequence
        else dest.append(escapes[pos].second);
    }
    // return output
    return dest;
}

string ContentType_t::unescape(const string &src) const {
    // output string
    string dest;
    dest.reserve(src.length());

    // run through input string
    for (string::const_iterator isrc = src.begin();
         isrc != src.end(); ) {
        // help iterator
        string::const_iterator bsrc = isrc;
        // index in automaton
        int state = 0;
        // run through remaining characters
        for (; bsrc != src.end(); ++bsrc) {
            // move to next state
            state = nextState(*bsrc, state);
            // we stop here if state is not positive
            if (state <= 0) break;
        }

        // check final state
        if (state < 0) {
            // state is negative => it's negated character!
            dest.push_back(-state);
            // move after so far eaten escape sequence
            isrc = bsrc + 1;
        } else {
            // sequence not matcher pass input verbatim to output
            dest.push_back(*isrc++);
        }
    }
    // return output
    return dest;
}

int ContentType_t::nextState(unsigned char c, int state) const {
    // stop when state outside automaton
    if ((static_cast<unsigned int>(state) >= unescaper.size()) ||
        (state < 0)) return 0;

    // iterator to state
    vector<pair<int, int> >::const_iterator i = unescaper.begin() + state;

    // find rule to move to next state
    for (; i->first > 0; ++i)
        if (i->first == c)
            return i->second;
    // no rule => stop
    return 0;
}

/**
 * @short State in unescaper automaton.
 */
struct UnescaperState_t {
    /**
     * @short Vector of next states.
     */
    typedef vector<UnescaperState_t> UnescaperStateVector_t;

    /**
     * @short Create new state.
     * @param rule matched character
     */
    UnescaperState_t(int rule = 0)
        : rule(rule), nextState(0)
    {}

    /**
     * @short Matched character.
     */
    int rule;

    /**
     * @short Next state of automaton (positive) or replacement
     *        character (negative) or stop state (zero).
     */
    int nextState;

    /**
     * @short Next states.
     */
    UnescaperStateVector_t nextStates;

    /**
     * @short Add new next state.
     * @param o rule for new state
     * @return added state
     */
    UnescaperState_t& add(int o) {
        // try to find existing state
        for (UnescaperStateVector_t::iterator i = nextStates.begin();
             i != nextStates.end(); ++i) {
            // rule found => return it
            if (i->rule == o) return *i;
        }
        // no state found, create it
        nextStates.push_back(UnescaperState_t(o));
        // return it
        return nextStates.back();
    }

    /**
     * @short Linearize automaton tree.
     */
    void linearize(vector<pair<int, int> > &unescaper) const {
        // remember link holders
        vector<int> referrers;
        // run throgh next states and write rule for each
        for (UnescaperStateVector_t::const_iterator i = nextStates.begin();
             i != nextStates.end(); ++i) {
            // remember position of link to next state
            referrers.push_back(unescaper.size());
            // create state
            unescaper.push_back(pair<int, int>(i->rule, -i->nextState));
        }
        // put STOP indicator when we have any next state
        if (nextState == 0)
            unescaper.push_back(pair<int, int>(0, 0));
        // run through all rules
        vector<int>::const_iterator ireferrers = referrers.begin();
        for (UnescaperStateVector_t::const_iterator i = nextStates.begin();
             i != nextStates.end(); ++i, ++ireferrers) {
            if (!i->nextState) {
                // remember start of next state
                int pos = unescaper.size();
                // linearize this state
                i->linearize(unescaper);
                // assign link to sub state
                unescaper[*ireferrers].second = pos;
            }
        }
    }

};

void ContentType_t::compileUnescaper() {
    // destroy current automaton
    unescaper.clear();

    // start state of automaton
    UnescaperState_t root;

    // run through escape definitions
    for (vector<pair<unsigned char, string> >::const_iterator
             iescapes = escapes.begin();
         iescapes != escapes.end(); ++iescapes) {
        // start state
        UnescaperState_t *state = &root;
        // escape sequence
        const string &str = iescapes->second;
        // run through escape sequence
        for (string::const_iterator istr = str.begin();
             istr != str.end(); ++istr) {
            // add next state
            state = &state->add(*istr);
        }
        // assign unescaped character as final rule
        state->nextState = iescapes->first;
    }
    // linearize automaton into vector
    root.linearize(unescaper);
}

/**
 * @short Function which creates content type descriptor
 * @return content type descriptor
 */
typedef ContentType_t* (*Creator_t)();

/**
 * @short Entry in table of content type descriptor creating
 * functions.
 */
struct CreatorEntry_t {
    /**
     * @short name of content type
     */
    const char *name;

    /**
     * @short content type descriptor creating function
     */
    Creator_t creator;

    /**
     * @short comment for this content type
     */
    const char *comment;
};

/** @short Create descriptor of HTML/XHTML/XML content type.
 * @return HTML descriptor
 */
ContentType_t* htmlCreator() {
    // create HTML descriptor
    ContentType_t *html = new ContentType_t();
    // HTML has only block comments
    html->blockComment = pair<string, string>("<!--", "-->");
    // and has these (necessary) escapes
    html->addEscape('&', "&");
    html->addEscape('<', "<");
    html->addEscape('>', ">");
    html->addEscape('"', """);
    // compile unescaping automaton
    html->compileUnescaper();
    // return descriptor
    return html;
}

/** @short Create descriptor of shell.
 * @return shell descriptor
 */
ContentType_t* shellCreator() {
    // create SHELL descriptor
    ContentType_t *shell = new ContentType_t();
    // SHELL has only line comment
    shell->lineComment = "#";
    // return descriptor
    return shell;
}

/** @short Create descriptor of C language.
 * @return C descriptor
 */
ContentType_t* cCreator() {
    // create C descriptor
    ContentType_t *c = new ContentType_t();
    // C has only block comments
    c->blockComment = pair<string, string>("/*", "*/");
    // return descriptor
    return c;
}

/** @short Create descriptor of quoted string.
 * @return quoted string descriptor
 */
ContentType_t* qstringCreator() {
    // create C descriptor
    ContentType_t *qs = new ContentType_t();

    qs->addEscape('\n', "\\n");
    qs->addEscape('\r', "\\r");
    qs->addEscape('\a', "\\a");
    qs->addEscape('\0', "\\0");
    qs->addEscape('\v', "\\v");
    qs->addEscape('\'', "\\'");
    qs->addEscape('"', "\\\"");

    // compile unescaping automaton
    qs->compileUnescaper();

    // return descriptor
    return qs;
}

static CreatorEntry_t creators[] = {
    { "text/html", htmlCreator,
      "Hypertext markup language. Same processor as for"
      " 'text/xhtml' and 'text/xml'" },
    { "text/xhtml", htmlCreator,
      "X hypertext markup language. Same processor as for"
      " 'text/xhtml' and 'text/xml'" },
    { "text/xml", htmlCreator,
      "Extensible markup language. Same processor as for"
      " 'text/xhtml' and 'text/xml'" },
    { "application/x-sh", shellCreator,
      "Common for all types of shell." },
    { "text/csrc", cCreator,
      "C/C++ source code" },
    { "quoted-string", qstringCreator,
      "Generic quoted string with escapes." },
    { 0, 0 }
};

const ContentType_t::Descriptor_t* ContentType_t::getDefault() {
    if (!descriptors) {
        descriptors = new map<string, Descriptor_t*>();
        descriptorIndex = new vector<ContentType_t::Descriptor_t*>();
    }

    if (!unknown) {
        string name("text/plain");
        unknown = new Descriptor_t(new ContentType_t(), 0,
                                   name, "Default (text/plain) type.");
        descriptors->insert(pair<string, Descriptor_t*>(name, unknown));
        descriptorIndex->push_back(unknown);
    }

    return unknown;
}

namespace {
    struct ToLower_t {
        int operator () (int c) const {
            return tolower(c);
        }
    };
}

const ContentType_t::Descriptor_t*
ContentType_t::findContentType(const string &sname, Error_t &err,
                               const Error_t::Position_t &pos, bool failOnError)
{
    // make name lower
    string name(sname);
    transform(name.begin(), name.end(), name.begin(), ToLower_t());

    // create all static data
    if (!unknown) getDefault();

    if (name.empty()) return unknown;

    // try to find cached content type descriptor
    map<string, ContentType_t::Descriptor_t*>::const_iterator
        fdescriptors = descriptors->find(name);
    // if no content descriptor found
    if (fdescriptors == descriptors->end()) {
        // run through creator table and try to find appropriate
        // creator
        for (CreatorEntry_t *icreators = creators; icreators->name;
             ++icreators) {
            // if creator found
            if (icreators->name == name) {
                // create content type descriptor
                Descriptor_t *descriptor =
                    new Descriptor_t(icreators->creator(),
                                     descriptorIndex->size(),
                                     icreators->name, icreators->comment);
                // remember descriptor in the descriptorIndex
                descriptorIndex->push_back(descriptor);

                // remmeber descriptor in the cache and return it to
                // the caller
                return descriptors->insert
                    (pair<string, Descriptor_t*>(name, descriptor))
                    .first->second;
            }
        }

        // log error
        err.logError(Error_t::LL_ERROR, pos, "Content type '" + sname +
                     "' not found.");

        // content type not known; return global desriptor for unknown
        // types or 0 when asked to faile
        return failOnError ? 0 : unknown;
    }

    // return cached entry
    return fdescriptors->second;
};

const ContentType_t::Descriptor_t*
ContentType_t::getContentType(unsigned int index)
{
    // check bounds and return desctiptor (or 0 on error)
    if (index >= descriptorIndex->size()) return 0;
    return (*descriptorIndex)[index];
}

void ContentType_t::listSupported(vector<pair<string, string> > &supported) {
    for (CreatorEntry_t *icreators = creators;
         icreators->name; ++icreators)
        supported.push_back(make_pair(icreators->name,
                                      (icreators->comment ? icreators->comment
                                      : string())));
};

void Escaper_t::push(ContentType_t *ct) {
    escapers.push(ct);
}

void Escaper_t::push(unsigned int index, Error_t &err,
                     const Error_t::Position_t &pos)
{
    const ContentType_t::Descriptor_t *descriptor
        = ContentType_t::getContentType(index);
    if (!descriptor) {
        err.logError(Error_t::LL_ERROR, pos,
                     "Cannot pot invalid content type -- using top instead.");
        escapers.push(escapers.top());
    } else {
        escapers.push(descriptor->contentType);
    }
}

void Escaper_t::pop(Error_t &err, const Error_t::Position_t &pos) {
    if (!escapers.empty()) {
        escapers.pop();
    } else {
        err.logError(Error_t::LL_ERROR, pos,
                     "Cannot pop content type -- only one remains.");
    }
}
<commit_msg>added escaping od \ to quoted-string escaper<commit_after>/*
 * Teng -- a general purpose templating engine.
 * Copyright (C) 2004  Seznam.cz, a.s.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Seznam.cz, a.s.
 * Naskove 1, Praha 5, 15000, Czech Republic
 * http://www.seznam.cz, mailto:teng@firma.seznam.cz
 *
 * $Id: tengcontenttype.cc,v 1.4 2005-09-26 10:08:21 vasek Exp $
 *
 * DESCRIPTION
 * Teng language descriptor -- implementation.
 *
 * AUTHORS
 * Vaclav Blazek <blazek@firma.seznam.cz>
 *
 * HISTORY
 * 2003-09-24  (vasek)
 *             Created.
 */

#include <string.h>
#include <utility>
#include <algorithm>
#include <ctype.h>

#include "tengcontenttype.h"

using namespace std;

using namespace Teng;

namespace {
    map<string, ContentType_t::Descriptor_t*> *descriptors = 0;
    vector<ContentType_t::Descriptor_t*> *descriptorIndex = 0;

    ContentType_t::Descriptor_t *unknown = 0;
}

ContentType_t::ContentType_t()
    : lineComment(), blockComment(), escapes(),
      unescaper()
{
    // set escape bitmap to all -1 (character not escaped)
    int *end = escapeBitmap + 256;
    int *i = escapeBitmap;
    while (i != end) *i++ = -1;
    // create empty automaton
    unescaper.push_back(pair<int, int>(0, 0));
}

int ContentType_t::addEscape(unsigned char c, const string &escape) {
    // if escape already present in bitmap make it error
    if (escapeBitmap[c] != -1) return -1;
    // add escape entry
    escapes.push_back(pair<unsigned char, string>(c, escape));
    // update entry in escape bitmap
    return escapeBitmap[c] = escapes.size() - 1;
}

string ContentType_t::escape(const string &src) const {
    // output string
    string dest;
    dest.reserve(src.length());

    // run through input string
    for (string::const_iterator isrc = src.begin();
         isrc != src.end(); ++isrc) {
        // find entry in bitmap
        int pos = escapeBitmap[static_cast<unsigned char>(*isrc)];
        // if position is negative pass source character to output
        if (pos < 0) dest.push_back(*isrc);
        // else append escape sequence
        else dest.append(escapes[pos].second);
    }
    // return output
    return dest;
}

string ContentType_t::unescape(const string &src) const {
    // output string
    string dest;
    dest.reserve(src.length());

    // run through input string
    for (string::const_iterator isrc = src.begin();
         isrc != src.end(); ) {
        // help iterator
        string::const_iterator bsrc = isrc;
        // index in automaton
        int state = 0;
        // run through remaining characters
        for (; bsrc != src.end(); ++bsrc) {
            // move to next state
            state = nextState(*bsrc, state);
            // we stop here if state is not positive
            if (state <= 0) break;
        }

        // check final state
        if (state < 0) {
            // state is negative => it's negated character!
            dest.push_back(-state);
            // move after so far eaten escape sequence
            isrc = bsrc + 1;
        } else {
            // sequence not matcher pass input verbatim to output
            dest.push_back(*isrc++);
        }
    }
    // return output
    return dest;
}

int ContentType_t::nextState(unsigned char c, int state) const {
    // stop when state outside automaton
    if ((static_cast<unsigned int>(state) >= unescaper.size()) ||
        (state < 0)) return 0;

    // iterator to state
    vector<pair<int, int> >::const_iterator i = unescaper.begin() + state;

    // find rule to move to next state
    for (; i->first > 0; ++i)
        if (i->first == c)
            return i->second;
    // no rule => stop
    return 0;
}

/**
 * @short State in unescaper automaton.
 */
struct UnescaperState_t {
    /**
     * @short Vector of next states.
     */
    typedef vector<UnescaperState_t> UnescaperStateVector_t;

    /**
     * @short Create new state.
     * @param rule matched character
     */
    UnescaperState_t(int rule = 0)
        : rule(rule), nextState(0)
    {}

    /**
     * @short Matched character.
     */
    int rule;

    /**
     * @short Next state of automaton (positive) or replacement
     *        character (negative) or stop state (zero).
     */
    int nextState;

    /**
     * @short Next states.
     */
    UnescaperStateVector_t nextStates;

    /**
     * @short Add new next state.
     * @param o rule for new state
     * @return added state
     */
    UnescaperState_t& add(int o) {
        // try to find existing state
        for (UnescaperStateVector_t::iterator i = nextStates.begin();
             i != nextStates.end(); ++i) {
            // rule found => return it
            if (i->rule == o) return *i;
        }
        // no state found, create it
        nextStates.push_back(UnescaperState_t(o));
        // return it
        return nextStates.back();
    }

    /**
     * @short Linearize automaton tree.
     */
    void linearize(vector<pair<int, int> > &unescaper) const {
        // remember link holders
        vector<int> referrers;
        // run throgh next states and write rule for each
        for (UnescaperStateVector_t::const_iterator i = nextStates.begin();
             i != nextStates.end(); ++i) {
            // remember position of link to next state
            referrers.push_back(unescaper.size());
            // create state
            unescaper.push_back(pair<int, int>(i->rule, -i->nextState));
        }
        // put STOP indicator when we have any next state
        if (nextState == 0)
            unescaper.push_back(pair<int, int>(0, 0));
        // run through all rules
        vector<int>::const_iterator ireferrers = referrers.begin();
        for (UnescaperStateVector_t::const_iterator i = nextStates.begin();
             i != nextStates.end(); ++i, ++ireferrers) {
            if (!i->nextState) {
                // remember start of next state
                int pos = unescaper.size();
                // linearize this state
                i->linearize(unescaper);
                // assign link to sub state
                unescaper[*ireferrers].second = pos;
            }
        }
    }

};

void ContentType_t::compileUnescaper() {
    // destroy current automaton
    unescaper.clear();

    // start state of automaton
    UnescaperState_t root;

    // run through escape definitions
    for (vector<pair<unsigned char, string> >::const_iterator
             iescapes = escapes.begin();
         iescapes != escapes.end(); ++iescapes) {
        // start state
        UnescaperState_t *state = &root;
        // escape sequence
        const string &str = iescapes->second;
        // run through escape sequence
        for (string::const_iterator istr = str.begin();
             istr != str.end(); ++istr) {
            // add next state
            state = &state->add(*istr);
        }
        // assign unescaped character as final rule
        state->nextState = iescapes->first;
    }
    // linearize automaton into vector
    root.linearize(unescaper);
}

/**
 * @short Function which creates content type descriptor
 * @return content type descriptor
 */
typedef ContentType_t* (*Creator_t)();

/**
 * @short Entry in table of content type descriptor creating
 * functions.
 */
struct CreatorEntry_t {
    /**
     * @short name of content type
     */
    const char *name;

    /**
     * @short content type descriptor creating function
     */
    Creator_t creator;

    /**
     * @short comment for this content type
     */
    const char *comment;
};

/** @short Create descriptor of HTML/XHTML/XML content type.
 * @return HTML descriptor
 */
ContentType_t* htmlCreator() {
    // create HTML descriptor
    ContentType_t *html = new ContentType_t();
    // HTML has only block comments
    html->blockComment = pair<string, string>("<!--", "-->");
    // and has these (necessary) escapes
    html->addEscape('&', "&");
    html->addEscape('<', "<");
    html->addEscape('>', ">");
    html->addEscape('"', """);
    // compile unescaping automaton
    html->compileUnescaper();
    // return descriptor
    return html;
}

/** @short Create descriptor of shell.
 * @return shell descriptor
 */
ContentType_t* shellCreator() {
    // create SHELL descriptor
    ContentType_t *shell = new ContentType_t();
    // SHELL has only line comment
    shell->lineComment = "#";
    // return descriptor
    return shell;
}

/** @short Create descriptor of C language.
 * @return C descriptor
 */
ContentType_t* cCreator() {
    // create C descriptor
    ContentType_t *c = new ContentType_t();
    // C has only block comments
    c->blockComment = pair<string, string>("/*", "*/");
    // return descriptor
    return c;
}

/** @short Create descriptor of quoted string.
 * @return quoted string descriptor
 */
ContentType_t* qstringCreator() {
    // create quoted-string descriptor
    ContentType_t *qs = new ContentType_t();

    qs->addEscape('\\', "\\\\");
    qs->addEscape('\n', "\\n");
    qs->addEscape('\r', "\\r");
    qs->addEscape('\a', "\\a");
    qs->addEscape('\0', "\\0");
    qs->addEscape('\v', "\\v");
    qs->addEscape('\'', "\\'");
    qs->addEscape('"', "\\\"");

    // compile unescaping automaton
    qs->compileUnescaper();

    // return descriptor
    return qs;
}

static CreatorEntry_t creators[] = {
    { "text/html", htmlCreator,
      "Hypertext markup language. Same processor as for"
      " 'text/xhtml' and 'text/xml'" },
    { "text/xhtml", htmlCreator,
      "X hypertext markup language. Same processor as for"
      " 'text/xhtml' and 'text/xml'" },
    { "text/xml", htmlCreator,
      "Extensible markup language. Same processor as for"
      " 'text/xhtml' and 'text/xml'" },
    { "application/x-sh", shellCreator,
      "Common for all types of shell." },
    { "text/csrc", cCreator,
      "C/C++ source code" },
    { "quoted-string", qstringCreator,
      "Generic quoted string with escapes." },
    { 0, 0 }
};

const ContentType_t::Descriptor_t* ContentType_t::getDefault() {
    if (!descriptors) {
        descriptors = new map<string, Descriptor_t*>();
        descriptorIndex = new vector<ContentType_t::Descriptor_t*>();
    }

    if (!unknown) {
        string name("text/plain");
        unknown = new Descriptor_t(new ContentType_t(), 0,
                                   name, "Default (text/plain) type.");
        descriptors->insert(pair<string, Descriptor_t*>(name, unknown));
        descriptorIndex->push_back(unknown);
    }

    return unknown;
}

namespace {
    struct ToLower_t {
        int operator () (int c) const {
            return tolower(c);
        }
    };
}

const ContentType_t::Descriptor_t*
ContentType_t::findContentType(const string &sname, Error_t &err,
                               const Error_t::Position_t &pos, bool failOnError)
{
    // make name lower
    string name(sname);
    transform(name.begin(), name.end(), name.begin(), ToLower_t());

    // create all static data
    if (!unknown) getDefault();

    if (name.empty()) return unknown;

    // try to find cached content type descriptor
    map<string, ContentType_t::Descriptor_t*>::const_iterator
        fdescriptors = descriptors->find(name);
    // if no content descriptor found
    if (fdescriptors == descriptors->end()) {
        // run through creator table and try to find appropriate
        // creator
        for (CreatorEntry_t *icreators = creators; icreators->name;
             ++icreators) {
            // if creator found
            if (icreators->name == name) {
                // create content type descriptor
                Descriptor_t *descriptor =
                    new Descriptor_t(icreators->creator(),
                                     descriptorIndex->size(),
                                     icreators->name, icreators->comment);
                // remember descriptor in the descriptorIndex
                descriptorIndex->push_back(descriptor);

                // remmeber descriptor in the cache and return it to
                // the caller
                return descriptors->insert
                    (pair<string, Descriptor_t*>(name, descriptor))
                    .first->second;
            }
        }

        // log error
        err.logError(Error_t::LL_ERROR, pos, "Content type '" + sname +
                     "' not found.");

        // content type not known; return global desriptor for unknown
        // types or 0 when asked to faile
        return failOnError ? 0 : unknown;
    }

    // return cached entry
    return fdescriptors->second;
};

const ContentType_t::Descriptor_t*
ContentType_t::getContentType(unsigned int index)
{
    // check bounds and return desctiptor (or 0 on error)
    if (index >= descriptorIndex->size()) return 0;
    return (*descriptorIndex)[index];
}

void ContentType_t::listSupported(vector<pair<string, string> > &supported) {
    for (CreatorEntry_t *icreators = creators;
         icreators->name; ++icreators)
        supported.push_back(make_pair(icreators->name,
                                      (icreators->comment ? icreators->comment
                                      : string())));
};

void Escaper_t::push(ContentType_t *ct) {
    escapers.push(ct);
}

void Escaper_t::push(unsigned int index, Error_t &err,
                     const Error_t::Position_t &pos)
{
    const ContentType_t::Descriptor_t *descriptor
        = ContentType_t::getContentType(index);
    if (!descriptor) {
        err.logError(Error_t::LL_ERROR, pos,
                     "Cannot pot invalid content type -- using top instead.");
        escapers.push(escapers.top());
    } else {
        escapers.push(descriptor->contentType);
    }
}

void Escaper_t::pop(Error_t &err, const Error_t::Position_t &pos) {
    if (!escapers.empty()) {
        escapers.pop();
    } else {
        err.logError(Error_t::LL_ERROR, pos,
                     "Cannot pop content type -- only one remains.");
    }
}
<|endoftext|>


<commit_before>// Copyright 2010-2014 RethinkDB, all rights reserved.
#include "btree/operations.hpp"

#include "btree/internal_node.hpp"
#include "btree/leaf_node.hpp"
#include "btree/node.hpp"
#include "btree/slice.hpp"
#include "buffer_cache/alt/alt.hpp"
#include "concurrency/promise.hpp"
#include "rdb_protocol/profile.hpp"

// TODO: consider B#/B* trees to improve space efficiency

/* Passing in a pass_back_superblock parameter will cause this function to
 * return the superblock after it's no longer needed (rather than releasing
 * it). Notice the superblock is not guaranteed to be returned until the
 * keyvalue_location_t that's passed in (keyvalue_location_out) is destroyed.
 * This is because it may need to use the superblock for some of its methods.
 * */
// KSI: It seems like really we should pass the superblock_t via rvalue reference.
// Is that possible?  (promise_t makes it hard.)
template <class Value>
void find_keyvalue_location_for_write(
        superblock_t *superblock, const btree_key_t *key,
        keyvalue_location_t<Value> *keyvalue_location_out,
        btree_stats_t *stats,
        profile::trace_t *trace,
        promise_t<superblock_t *> *pass_back_superblock = NULL) {
    value_sizer_t<Value> sizer(superblock->cache()->max_block_size());

    keyvalue_location_out->superblock = superblock;
    keyvalue_location_out->pass_back_superblock = pass_back_superblock;

    ensure_stat_block(superblock);
    keyvalue_location_out->stat_block = keyvalue_location_out->superblock->get_stat_block_id();

    keyvalue_location_out->stats = stats;

    // KSI: Make sure we do the logic smart here -- don't needlessly hold both
    // buffers.  (This finds the keyvalue for _write_ so that probably won't really
    // happen.)
    buf_lock_t last_buf;
    buf_lock_t buf;
    {
        // KSI: We can't acquire the block for write here -- we could, but it would
        // worsen the performance of the program -- sometimes we only end up using
        // this block for read.  So the profiling information is not very good.
        profile::starter_t starter("Acquiring block for write.\n", trace);
        buf = get_root(&sizer, superblock);
    }

    // Walk down the tree to the leaf.
    for (;;) {
        {
            buf_read_t read(&buf);
            if (!node::is_internal(static_cast<const node_t *>(read.get_data_read()))) {
                break;
            }
        }
        // Check if the node is overfull and proactively split it if it is (since this is an internal node).
        {
            profile::starter_t starter("Perhaps split node.", trace);
            check_and_handle_split(&sizer, &buf, &last_buf, superblock, key, static_cast<Value *>(NULL));
        }

        // Check if the node is underfull, and merge/level if it is.
        {
            profile::starter_t starter("Perhaps merge nodes.", trace);
            check_and_handle_underfull(&sizer, &buf, &last_buf, superblock, key);
        }

        // Release the superblock, if we've gone past the root (and haven't
        // already released it). If we're still at the root or at one of
        // its direct children, we might still want to replace the root, so
        // we can't release the superblock yet.
        if (!last_buf.empty() && keyvalue_location_out->superblock) {
            if (pass_back_superblock != NULL) {
                pass_back_superblock->pulse(superblock);
                keyvalue_location_out->superblock = NULL;
            } else {
                keyvalue_location_out->superblock->release();
                keyvalue_location_out->superblock = NULL;
            }
        }

        // Release the old previous node (unless we're at the root), and set
        // the next previous node (which is the current node).
        last_buf.reset_buf_lock();

        // Look up and acquire the next node.
        block_id_t node_id;
        {
            buf_read_t read(&buf);
            auto node = static_cast<const internal_node_t *>(read.get_data_read());
            node_id = internal_node::lookup(node, key);
        }
        rassert(node_id != NULL_BLOCK_ID && node_id != SUPERBLOCK_ID);

        {
            profile::starter_t starter("Acquiring block for write.\n", trace);
            buf_lock_t tmp(&buf, node_id, access_t::write);
            last_buf = std::move(buf);
            buf = std::move(tmp);
        }
    }

    {
        scoped_malloc_t<Value> tmp(sizer.max_possible_size());

        // We've gone down the tree and gotten to a leaf. Now look up the key.
        buf_read_t read(&buf);
        auto node = static_cast<const leaf_node_t *>(read.get_data_read());
        bool key_found = leaf::lookup(&sizer, node, key, tmp.get());

        if (key_found) {
            keyvalue_location_out->there_originally_was_value = true;
            keyvalue_location_out->value = std::move(tmp);
        }
    }

    keyvalue_location_out->last_buf.swap(last_buf);
    keyvalue_location_out->buf.swap(buf);
}

template <class Value>
void find_keyvalue_location_for_read(
        superblock_t *superblock, const btree_key_t *key,
        keyvalue_location_t<Value> *keyvalue_location_out,
        btree_stats_t *stats, profile::trace_t *trace) {
    stats->pm_keys_read.record();
    value_sizer_t<Value> sizer(superblock->cache()->max_block_size());

    const block_id_t root_id = superblock->get_root_block_id();
    rassert(root_id != SUPERBLOCK_ID);

    if (root_id == NULL_BLOCK_ID) {
        // There is no root, so the tree is empty.
        superblock->release();
        return;
    }

    buf_lock_t buf;
    {
        profile::starter_t starter("Acquire a block for read.", trace);
        buf_lock_t tmp(superblock->expose_buf(), root_id, access_t::read);
        superblock->release();
        buf = std::move(tmp);
    }

#ifndef NDEBUG
    {
        buf_read_t read(&buf);
        node::validate(&sizer, static_cast<const node_t *>(read.get_data_read()));
    }
#endif  // NDEBUG

    for (;;) {
        {
            buf_read_t read(&buf);
            if (!node::is_internal(static_cast<const node_t *>(read.get_data_read()))) {
                break;
            }
        }
        block_id_t node_id;
        {
            buf_read_t read(&buf);
            const internal_node_t *node
                = static_cast<const internal_node_t *>(read.get_data_read());
            node_id = internal_node::lookup(node, key);
        }
        rassert(node_id != NULL_BLOCK_ID && node_id != SUPERBLOCK_ID);

        {
            profile::starter_t starter("Acquire a block for read.", trace);
            buf_lock_t tmp(&buf, node_id, access_t::read);
            buf.reset_buf_lock();
            buf = std::move(tmp);
        }

#ifndef NDEBUG
        {
            buf_read_t read(&buf);
            node::validate(&sizer, static_cast<const node_t *>(read.get_data_read()));
        }
#endif  // NDEBUG
    }

    // Got down to the leaf, now probe it.
    scoped_malloc_t<Value> value(sizer.max_possible_size());
    bool value_found;
    {
        buf_read_t read(&buf);
        const leaf_node_t *leaf
            = static_cast<const leaf_node_t *>(read.get_data_read());
        value_found = leaf::lookup(&sizer, leaf, key, value.get());
    }
    if (value_found) {
        keyvalue_location_out->buf = std::move(buf);
        keyvalue_location_out->there_originally_was_value = true;
        keyvalue_location_out->value = std::move(value);
    }
}

enum class expired_t { NO, YES };

template <class Value>
void apply_keyvalue_change(keyvalue_location_t<Value> *kv_loc,
        const btree_key_t *key, repli_timestamp_t tstamp, expired_t expired,
        key_modification_callback_t<Value> *km_callback) {

    value_sizer_t<Value> sizer(kv_loc->buf.cache()->get_block_size());

    key_modification_proof_t km_proof
        = km_callback->value_modification(kv_loc, key);

    /* how much this keyvalue change affects the total population of the btree
     * (should be -1, 0 or 1) */
    int population_change;

    if (kv_loc->value.has()) {
        // We have a value to insert.

        // Split the node if necessary, to make sure that we have room
        // for the value.  Not necessary when deleting, because the
        // node won't grow.

        check_and_handle_split(&sizer, &kv_loc->buf, &kv_loc->last_buf,
                               kv_loc->superblock, key, kv_loc->value.get());

        {
#ifndef NDEBUG
            buf_read_t read(&kv_loc->buf);
            auto leaf_node = static_cast<const leaf_node_t *>(read.get_data_read());
            rassert(!leaf::is_full(&sizer, leaf_node, key, kv_loc->value.get()));
#endif
        }

        if (kv_loc->there_originally_was_value) {
            population_change = 0;
        } else {
            population_change = 1;
        }

        {
            buf_write_t write(&kv_loc->buf);
            auto leaf_node = static_cast<leaf_node_t *>(write.get_data_write());
            leaf::insert(&sizer,
                         leaf_node,
                         key,
                         kv_loc->value.get(),
                         tstamp,
                         km_proof);
        }

        kv_loc->stats->pm_keys_set.record();
    } else {
        // Delete the value if it's there.
        if (kv_loc->there_originally_was_value) {
            if (expired == expired_t::NO) {
                rassert(tstamp != repli_timestamp_t::invalid, "Deletes need a valid timestamp now.");
                {
                    buf_write_t write(&kv_loc->buf);
                    auto leaf_node = static_cast<leaf_node_t *>(write.get_data_write());
                    leaf::remove(&sizer,
                                 leaf_node,
                                 key,
                                 tstamp,
                                 km_proof);
                }
                population_change = -1;
                kv_loc->stats->pm_keys_set.record();
            } else {
                // TODO: Oh god oh god get rid of "expired".
                // Expirations do an erase, not a delete.
                {
                    buf_write_t write(&kv_loc->buf);
                    auto leaf_node = static_cast<leaf_node_t *>(write.get_data_write());
                    leaf::erase_presence(&sizer,
                                         leaf_node,
                                         key,
                                         km_proof);
                }
                population_change = 0;
                kv_loc->stats->pm_keys_expired.record();
            }
        } else {
            population_change = 0;
        }
    }

    // Check to see if the leaf is underfull (following a change in
    // size or a deletion, and merge/level if it is.
    check_and_handle_underfull(&sizer, &kv_loc->buf, &kv_loc->last_buf,
                               kv_loc->superblock, key);

    // Modify the stats block.  The stats block is detached from the rest of the
    // btree, we don't keep a consistent view of it, so we pass the txn as its
    // parent.
    buf_lock_t stat_block(buf_parent_t(kv_loc->buf.txn()),
                          kv_loc->stat_block, access_t::write);
    buf_write_t stat_block_write(&stat_block);
    auto stat_block_buf
        = static_cast<btree_statblock_t *>(stat_block_write.get_data_write());
    stat_block_buf->population += population_change;
}
<commit_msg>Reduced buf_read_t construction a bit.<commit_after>// Copyright 2010-2014 RethinkDB, all rights reserved.
#include "btree/operations.hpp"

#include "btree/internal_node.hpp"
#include "btree/leaf_node.hpp"
#include "btree/node.hpp"
#include "btree/slice.hpp"
#include "buffer_cache/alt/alt.hpp"
#include "concurrency/promise.hpp"
#include "rdb_protocol/profile.hpp"

// TODO: consider B#/B* trees to improve space efficiency

/* Passing in a pass_back_superblock parameter will cause this function to
 * return the superblock after it's no longer needed (rather than releasing
 * it). Notice the superblock is not guaranteed to be returned until the
 * keyvalue_location_t that's passed in (keyvalue_location_out) is destroyed.
 * This is because it may need to use the superblock for some of its methods.
 * */
// KSI: It seems like really we should pass the superblock_t via rvalue reference.
// Is that possible?  (promise_t makes it hard.)
template <class Value>
void find_keyvalue_location_for_write(
        superblock_t *superblock, const btree_key_t *key,
        keyvalue_location_t<Value> *keyvalue_location_out,
        btree_stats_t *stats,
        profile::trace_t *trace,
        promise_t<superblock_t *> *pass_back_superblock = NULL) {
    value_sizer_t<Value> sizer(superblock->cache()->max_block_size());

    keyvalue_location_out->superblock = superblock;
    keyvalue_location_out->pass_back_superblock = pass_back_superblock;

    ensure_stat_block(superblock);
    keyvalue_location_out->stat_block = keyvalue_location_out->superblock->get_stat_block_id();

    keyvalue_location_out->stats = stats;

    // KSI: Make sure we do the logic smart here -- don't needlessly hold both
    // buffers.  (This finds the keyvalue for _write_ so that probably won't really
    // happen.)
    buf_lock_t last_buf;
    buf_lock_t buf;
    {
        // KSI: We can't acquire the block for write here -- we could, but it would
        // worsen the performance of the program -- sometimes we only end up using
        // this block for read.  So the profiling information is not very good.
        profile::starter_t starter("Acquiring block for write.\n", trace);
        buf = get_root(&sizer, superblock);
    }

    // Walk down the tree to the leaf.
    for (;;) {
        {
            buf_read_t read(&buf);
            if (!node::is_internal(static_cast<const node_t *>(read.get_data_read()))) {
                break;
            }
        }
        // Check if the node is overfull and proactively split it if it is (since this is an internal node).
        {
            profile::starter_t starter("Perhaps split node.", trace);
            check_and_handle_split(&sizer, &buf, &last_buf, superblock, key, static_cast<Value *>(NULL));
        }

        // Check if the node is underfull, and merge/level if it is.
        {
            profile::starter_t starter("Perhaps merge nodes.", trace);
            check_and_handle_underfull(&sizer, &buf, &last_buf, superblock, key);
        }

        // Release the superblock, if we've gone past the root (and haven't
        // already released it). If we're still at the root or at one of
        // its direct children, we might still want to replace the root, so
        // we can't release the superblock yet.
        if (!last_buf.empty() && keyvalue_location_out->superblock) {
            if (pass_back_superblock != NULL) {
                pass_back_superblock->pulse(superblock);
                keyvalue_location_out->superblock = NULL;
            } else {
                keyvalue_location_out->superblock->release();
                keyvalue_location_out->superblock = NULL;
            }
        }

        // Release the old previous node (unless we're at the root), and set
        // the next previous node (which is the current node).
        last_buf.reset_buf_lock();

        // Look up and acquire the next node.
        block_id_t node_id;
        {
            buf_read_t read(&buf);
            auto node = static_cast<const internal_node_t *>(read.get_data_read());
            node_id = internal_node::lookup(node, key);
        }
        rassert(node_id != NULL_BLOCK_ID && node_id != SUPERBLOCK_ID);

        {
            profile::starter_t starter("Acquiring block for write.\n", trace);
            buf_lock_t tmp(&buf, node_id, access_t::write);
            last_buf = std::move(buf);
            buf = std::move(tmp);
        }
    }

    {
        scoped_malloc_t<Value> tmp(sizer.max_possible_size());

        // We've gone down the tree and gotten to a leaf. Now look up the key.
        buf_read_t read(&buf);
        auto node = static_cast<const leaf_node_t *>(read.get_data_read());
        bool key_found = leaf::lookup(&sizer, node, key, tmp.get());

        if (key_found) {
            keyvalue_location_out->there_originally_was_value = true;
            keyvalue_location_out->value = std::move(tmp);
        }
    }

    keyvalue_location_out->last_buf.swap(last_buf);
    keyvalue_location_out->buf.swap(buf);
}

template <class Value>
void find_keyvalue_location_for_read(
        superblock_t *superblock, const btree_key_t *key,
        keyvalue_location_t<Value> *keyvalue_location_out,
        btree_stats_t *stats, profile::trace_t *trace) {
    stats->pm_keys_read.record();
    value_sizer_t<Value> sizer(superblock->cache()->max_block_size());

    const block_id_t root_id = superblock->get_root_block_id();
    rassert(root_id != SUPERBLOCK_ID);

    if (root_id == NULL_BLOCK_ID) {
        // There is no root, so the tree is empty.
        superblock->release();
        return;
    }

    buf_lock_t buf;
    {
        profile::starter_t starter("Acquire a block for read.", trace);
        buf_lock_t tmp(superblock->expose_buf(), root_id, access_t::read);
        superblock->release();
        buf = std::move(tmp);
    }

#ifndef NDEBUG
    {
        buf_read_t read(&buf);
        node::validate(&sizer, static_cast<const node_t *>(read.get_data_read()));
    }
#endif  // NDEBUG

    for (;;) {
        block_id_t node_id;
        {
            buf_read_t read(&buf);
            const void *data = read.get_data_read();
            if (!node::is_internal(static_cast<const node_t *>(data))) {
                break;
            }

            node_id = internal_node::lookup(static_cast<const internal_node_t *>(data),
                                            key);
        }
        rassert(node_id != NULL_BLOCK_ID && node_id != SUPERBLOCK_ID);

        {
            profile::starter_t starter("Acquire a block for read.", trace);
            buf_lock_t tmp(&buf, node_id, access_t::read);
            buf.reset_buf_lock();
            buf = std::move(tmp);
        }

#ifndef NDEBUG
        {
            buf_read_t read(&buf);
            node::validate(&sizer, static_cast<const node_t *>(read.get_data_read()));
        }
#endif  // NDEBUG
    }

    // Got down to the leaf, now probe it.
    scoped_malloc_t<Value> value(sizer.max_possible_size());
    bool value_found;
    {
        buf_read_t read(&buf);
        const leaf_node_t *leaf
            = static_cast<const leaf_node_t *>(read.get_data_read());
        value_found = leaf::lookup(&sizer, leaf, key, value.get());
    }
    if (value_found) {
        keyvalue_location_out->buf = std::move(buf);
        keyvalue_location_out->there_originally_was_value = true;
        keyvalue_location_out->value = std::move(value);
    }
}

enum class expired_t { NO, YES };

template <class Value>
void apply_keyvalue_change(keyvalue_location_t<Value> *kv_loc,
        const btree_key_t *key, repli_timestamp_t tstamp, expired_t expired,
        key_modification_callback_t<Value> *km_callback) {

    value_sizer_t<Value> sizer(kv_loc->buf.cache()->get_block_size());

    key_modification_proof_t km_proof
        = km_callback->value_modification(kv_loc, key);

    /* how much this keyvalue change affects the total population of the btree
     * (should be -1, 0 or 1) */
    int population_change;

    if (kv_loc->value.has()) {
        // We have a value to insert.

        // Split the node if necessary, to make sure that we have room
        // for the value.  Not necessary when deleting, because the
        // node won't grow.

        check_and_handle_split(&sizer, &kv_loc->buf, &kv_loc->last_buf,
                               kv_loc->superblock, key, kv_loc->value.get());

        {
#ifndef NDEBUG
            buf_read_t read(&kv_loc->buf);
            auto leaf_node = static_cast<const leaf_node_t *>(read.get_data_read());
            rassert(!leaf::is_full(&sizer, leaf_node, key, kv_loc->value.get()));
#endif
        }

        if (kv_loc->there_originally_was_value) {
            population_change = 0;
        } else {
            population_change = 1;
        }

        {
            buf_write_t write(&kv_loc->buf);
            auto leaf_node = static_cast<leaf_node_t *>(write.get_data_write());
            leaf::insert(&sizer,
                         leaf_node,
                         key,
                         kv_loc->value.get(),
                         tstamp,
                         km_proof);
        }

        kv_loc->stats->pm_keys_set.record();
    } else {
        // Delete the value if it's there.
        if (kv_loc->there_originally_was_value) {
            if (expired == expired_t::NO) {
                rassert(tstamp != repli_timestamp_t::invalid, "Deletes need a valid timestamp now.");
                {
                    buf_write_t write(&kv_loc->buf);
                    auto leaf_node = static_cast<leaf_node_t *>(write.get_data_write());
                    leaf::remove(&sizer,
                                 leaf_node,
                                 key,
                                 tstamp,
                                 km_proof);
                }
                population_change = -1;
                kv_loc->stats->pm_keys_set.record();
            } else {
                // TODO: Oh god oh god get rid of "expired".
                // Expirations do an erase, not a delete.
                {
                    buf_write_t write(&kv_loc->buf);
                    auto leaf_node = static_cast<leaf_node_t *>(write.get_data_write());
                    leaf::erase_presence(&sizer,
                                         leaf_node,
                                         key,
                                         km_proof);
                }
                population_change = 0;
                kv_loc->stats->pm_keys_expired.record();
            }
        } else {
            population_change = 0;
        }
    }

    // Check to see if the leaf is underfull (following a change in
    // size or a deletion, and merge/level if it is.
    check_and_handle_underfull(&sizer, &kv_loc->buf, &kv_loc->last_buf,
                               kv_loc->superblock, key);

    // Modify the stats block.  The stats block is detached from the rest of the
    // btree, we don't keep a consistent view of it, so we pass the txn as its
    // parent.
    buf_lock_t stat_block(buf_parent_t(kv_loc->buf.txn()),
                          kv_loc->stat_block, access_t::write);
    buf_write_t stat_block_write(&stat_block);
    auto stat_block_buf
        = static_cast<btree_statblock_t *>(stat_block_write.get_data_write());
    stat_block_buf->population += population_change;
}
<|endoftext|>


<commit_before>// Copyright (c) 2015-2017 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <chain.h>
#include <chainparams.h>
#include <pow.h>
#include <random.h>
#include <util.h>
#include <test/test_bitcoin.h>

#include <boost/test/unit_test.hpp>

BOOST_FIXTURE_TEST_SUITE(pow_tests, BasicTestingSetup)

/* Test calculation of next difficulty target with no constraints applying */
BOOST_AUTO_TEST_CASE(get_next_work)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    int64_t nLastRetargetTime = 1261130161; // Block #30240
    CBlockIndex pindexLast;
    pindexLast.nHeight = 32255;
    pindexLast.nTime = 1262152739;  // Block #32255
    pindexLast.nBits = 0x1d00ffff;
    BOOST_CHECK_EQUAL(GetNextTargetRequired(&pindexLast, false, chainParams->GetConsensus()), 0x1d00d86a);
}

/* Test the constraint on the upper bound for next work */
BOOST_AUTO_TEST_CASE(get_next_work_pow_limit)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    int64_t nLastRetargetTime = 1231006505; // Block #0
    CBlockIndex pindexLast;
    pindexLast.nHeight = 2015;
    pindexLast.nTime = 1233061996;  // Block #2015
    pindexLast.nBits = 0x1d00ffff;
    BOOST_CHECK_EQUAL(GetNextTargetRequired(&pindexLast, false, chainParams->GetConsensus()), 0x1d00ffff);
}

/* Test the constraint on the lower bound for actual time taken */
BOOST_AUTO_TEST_CASE(get_next_work_lower_limit_actual)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    int64_t nLastRetargetTime = 1279008237; // Block #66528
    CBlockIndex pindexLast;
    pindexLast.nHeight = 68543;
    pindexLast.nTime = 1279297671;  // Block #68543
    pindexLast.nBits = 0x1c05a3f4;
    BOOST_CHECK_EQUAL(GetNextTargetRequired(&pindexLast, false, chainParams->GetConsensus()), 0x1c0168fd);
}

/* Test the constraint on the upper bound for actual time taken */
BOOST_AUTO_TEST_CASE(get_next_work_upper_limit_actual)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    int64_t nLastRetargetTime = 1263163443; // NOTE: Not an actual block time
    CBlockIndex pindexLast;
    pindexLast.nHeight = 46367;
    pindexLast.nTime = 1269211443;  // Block #46367
    pindexLast.nBits = 0x1c387f6f;
    BOOST_CHECK_EQUAL(GetNextTargetRequired(&pindexLast, false, chainParams->GetConsensus()), 0x1d00e1fd);
}

BOOST_AUTO_TEST_CASE(GetBlockProofEquivalentTime_test)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    std::vector<CBlockIndex> blocks(10000);
    for (int i = 0; i < 10000; i++) {
        blocks[i].pprev = i ? &blocks[i - 1] : nullptr;
        blocks[i].nHeight = i;
        blocks[i].nTime = 1269211443 + i * chainParams->GetConsensus().nPowTargetSpacing;
        blocks[i].nBits = 0x207fffff; /* target 0x7fffff000... */
        blocks[i].nChainTrust = i ? blocks[i - 1].nChainTrust + GetBlockTrust(blocks[i - 1]) : arith_uint256(0);
    }

    for (int j = 0; j < 1000; j++) {
        CBlockIndex *p1 = &blocks[InsecureRandRange(10000)];
        CBlockIndex *p2 = &blocks[InsecureRandRange(10000)];
        CBlockIndex *p3 = &blocks[InsecureRandRange(10000)];

        int64_t tdiff = GetBlockProofEquivalentTime(*p1, *p2, *p3, chainParams->GetConsensus());
        BOOST_CHECK_EQUAL(tdiff, p1->GetBlockTime() - p2->GetBlockTime());
    }
}

BOOST_AUTO_TEST_SUITE_END()
<commit_msg>pow test for v9 fork added<commit_after>// Copyright (c) 2015-2017 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <chain.h>
#include <chainparams.h>
#include <pow.h>
#include <random.h>
#include <util.h>
#include <test/test_bitcoin.h>

#include <boost/test/unit_test.hpp>

BOOST_FIXTURE_TEST_SUITE(pow_tests, BasicTestingSetup)

/* Test calculation of next difficulty target with no constraints applying */
BOOST_AUTO_TEST_CASE(get_next_work)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);

    CBlockIndex pindexThirdLast;
    pindexThirdLast.nHeight = 2;
    pindexThirdLast.nTime = 1345400368;
    pindexThirdLast.nBits = 0x1c00ffff;

    CBlockIndex pindexSecondLast;
    pindexSecondLast.nHeight = 3;
    pindexSecondLast.pprev = &pindexThirdLast;
    pindexSecondLast.nTime = 1345400724;
    pindexSecondLast.nBits = 0x1c00ff7f;

    CBlockIndex pindexLast;
    pindexLast.nHeight = 4;
    pindexLast.pprev = &pindexSecondLast;
    pindexLast.nTime = 1345400851;
    pindexLast.nBits = 0x1c00ff4a;

    BOOST_CHECK_EQUAL(GetNextTargetRequired(&pindexLast, false, chainParams->GetConsensus()), 0x1c00fee3);
}

/* Test the target before v9 */
BOOST_AUTO_TEST_CASE(get_next_work_beforev9)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);

    CBlockIndex pindexThirdLast;
    pindexThirdLast.nHeight = 2;
    pindexThirdLast.nTime = 1581334360;
    pindexThirdLast.nBits = 0x1c00ffff;

    CBlockIndex pindexSecondLast;
    pindexSecondLast.nHeight = 3;
    pindexSecondLast.pprev = &pindexThirdLast;
    pindexSecondLast.nTime = 1581334380;
    pindexSecondLast.nBits = 0x1c00ff7f;

    CBlockIndex pindexLast;
    pindexLast.nHeight = 4;
    pindexLast.pprev = &pindexSecondLast;
    pindexLast.nTime = 1581334400;
    pindexLast.nBits = 0x1c00ff4a;

    BOOST_CHECK_EQUAL(GetNextTargetRequired(&pindexLast, false, chainParams->GetConsensus()), 0x1c00fecc);
}

/* Test the target correct after v9 */
BOOST_AUTO_TEST_CASE(get_next_work_afterv9)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);

    CBlockIndex pindexThirdLast;
    pindexThirdLast.nHeight = 2;
    pindexThirdLast.nTime = 1588334400;
    pindexThirdLast.nBits = 0x1c00ffff;

    CBlockIndex pindexSecondLast;
    pindexSecondLast.nHeight = 3;
    pindexSecondLast.pprev = &pindexThirdLast;
    pindexSecondLast.nTime = 1588334420;
    pindexSecondLast.nBits = 0x1c00ff7f;

    CBlockIndex pindexLast;
    pindexLast.nHeight = 4;
    pindexLast.pprev = &pindexSecondLast;
    pindexLast.nTime = 1588334440;
    pindexLast.nBits = 0x1c00ff4a;

    BOOST_CHECK_EQUAL(GetNextTargetRequired(&pindexLast, false, chainParams->GetConsensus()), 0x1c00f94c);
}

BOOST_AUTO_TEST_CASE(GetBlockProofEquivalentTime_test)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    std::vector<CBlockIndex> blocks(10000);
    for (int i = 0; i < 10000; i++) {
        blocks[i].pprev = i ? &blocks[i - 1] : nullptr;
        blocks[i].nHeight = i;
        blocks[i].nTime = 1269211443 + i * chainParams->GetConsensus().nPowTargetSpacing;
        blocks[i].nBits = 0x207fffff; /* target 0x7fffff000... */
        blocks[i].nChainTrust = i ? blocks[i - 1].nChainTrust + GetBlockTrust(blocks[i - 1]) : arith_uint256(0);
    }

    for (int j = 0; j < 1000; j++) {
        CBlockIndex *p1 = &blocks[InsecureRandRange(10000)];
        CBlockIndex *p2 = &blocks[InsecureRandRange(10000)];
        CBlockIndex *p3 = &blocks[InsecureRandRange(10000)];

        int64_t tdiff = GetBlockProofEquivalentTime(*p1, *p2, *p3, chainParams->GetConsensus());
        BOOST_CHECK_EQUAL(tdiff, p1->GetBlockTime() - p2->GetBlockTime());
    }
}

BOOST_AUTO_TEST_SUITE_END()
<|endoftext|>


<commit_before>// Copyright (c) 2012-2013 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "rpcserver.h"
#include "rpcclient.h"

#include "base58.h"
#include "netbase.h"

#include "test/test_bitcoin.h"

#include <boost/algorithm/string.hpp>
#include <boost/test/unit_test.hpp>

#include "univalue/univalue.h"

using namespace std;

UniValue
createArgs(int nRequired, const char* address1=NULL, const char* address2=NULL)
{
    UniValue result(UniValue::VARR);
    result.push_back(nRequired);
    UniValue addresses(UniValue::VARR);
    if (address1) addresses.push_back(address1);
    if (address2) addresses.push_back(address2);
    result.push_back(addresses);
    return result;
}

UniValue CallRPC(string args)
{
    vector<string> vArgs;
    boost::split(vArgs, args, boost::is_any_of(" \t"));
    string strMethod = vArgs[0];
    vArgs.erase(vArgs.begin());
    UniValue params = RPCConvertValues(strMethod, vArgs);

    rpcfn_type method = tableRPC[strMethod]->actor;
    try {
        UniValue result = (*method)(params, false);
        return result;
    }
    catch (const UniValue& objError) {
        throw runtime_error(find_value(objError, "message").get_str());
    }
}

BOOST_FIXTURE_TEST_SUITE(rpc_tests, TestingSetup)

BOOST_AUTO_TEST_CASE(rpc_rawparams)
{
    // Test raw transaction API argument handling
    UniValue r;

    BOOST_CHECK_THROW(CallRPC("getrawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("getrawtransaction not_hex"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("getrawtransaction a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed not_int"), runtime_error);

    BOOST_CHECK_THROW(CallRPC("createrawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("createrawtransaction null null"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("createrawtransaction not_array"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("createrawtransaction [] []"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("createrawtransaction {} {}"), runtime_error);
    BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [] {}"));
    BOOST_CHECK_THROW(CallRPC("createrawtransaction [] {} extra"), runtime_error);

    BOOST_CHECK_THROW(CallRPC("decoderawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("decoderawtransaction null"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("decoderawtransaction DEADBEEF"), runtime_error);
    string rawtx = "0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000";
    BOOST_CHECK_NO_THROW(r = CallRPC(string("decoderawtransaction ")+rawtx));
    BOOST_CHECK_EQUAL(find_value(r.get_obj(), "version").get_int(), 1);
    BOOST_CHECK_EQUAL(find_value(r.get_obj(), "locktime").get_int(), 0);
    BOOST_CHECK_THROW(r = CallRPC(string("decoderawtransaction ")+rawtx+" extra"), runtime_error);

    BOOST_CHECK_THROW(CallRPC("signrawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("signrawtransaction null"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("signrawtransaction ff00"), runtime_error);
    BOOST_CHECK_NO_THROW(CallRPC(string("signrawtransaction ")+rawtx));
    BOOST_CHECK_NO_THROW(CallRPC(string("signrawtransaction ")+rawtx+" null null NONE|ANYONECANPAY"));
    BOOST_CHECK_NO_THROW(CallRPC(string("signrawtransaction ")+rawtx+" [] [] NONE|ANYONECANPAY"));
    BOOST_CHECK_THROW(CallRPC(string("signrawtransaction ")+rawtx+" null null badenum"), runtime_error);

    // Only check failure cases for sendrawtransaction, there's no network to send to...
    BOOST_CHECK_THROW(CallRPC("sendrawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("sendrawtransaction null"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("sendrawtransaction DEADBEEF"), runtime_error);
    BOOST_CHECK_THROW(CallRPC(string("sendrawtransaction ")+rawtx+" extra"), runtime_error);
}

BOOST_AUTO_TEST_CASE(rpc_rawsign)
{
    UniValue r;
    // input is a 1-of-2 multisig (so is output):
    string prevout =
      "[{\"txid\":\"b4cc287e58f87cdae59417329f710f3ecd75a4ee1d2872b7248f50977c8493f3\","
      "\"vout\":1,\"scriptPubKey\":\"a914b10c9df5f7edf436c697f02f1efdba4cf399615187\","
      "\"redeemScript\":\"512103debedc17b3df2badbcdd86d5feb4562b86fe182e5998abd8bcd4f122c6155b1b21027e940bb73ab8732bfdf7f9216ecefca5b94d6df834e77e108f68e66f126044c052ae\"}]";
    r = CallRPC(string("createrawtransaction ")+prevout+" "+
      "{\"3HqAe9LtNBjnsfM4CyYaWTnvCaUYT7v4oZ\":11}");
    string notsigned = r.get_str();
    string privkey1 = "\"KzsXybp9jX64P5ekX1KUxRQ79Jht9uzW7LorgwE65i5rWACL6LQe\"";
    string privkey2 = "\"Kyhdf5LuKTRx4ge69ybABsiUAWjVRK4XGxAKk2FQLp2HjGMy87Z4\"";
    r = CallRPC(string("signrawtransaction ")+notsigned+" "+prevout+" "+"[]");
    BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == false);
    r = CallRPC(string("signrawtransaction ")+notsigned+" "+prevout+" "+"["+privkey1+","+privkey2+"]");
    BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == true);
}

BOOST_AUTO_TEST_CASE(rpc_format_monetary_values)
{
    BOOST_CHECK(ValueFromAmount(0LL).write() == "0.00000000");
    BOOST_CHECK(ValueFromAmount(1LL).write() == "0.00000001");
    BOOST_CHECK(ValueFromAmount(17622195LL).write() == "0.17622195");
    BOOST_CHECK(ValueFromAmount(50000000LL).write() == "0.50000000");
    BOOST_CHECK(ValueFromAmount(89898989LL).write() == "0.89898989");
    BOOST_CHECK(ValueFromAmount(100000000LL).write() == "1.00000000");
    BOOST_CHECK(ValueFromAmount(2099999999999990LL).write() == "20999999.99999990");
    BOOST_CHECK(ValueFromAmount(2099999999999999LL).write() == "20999999.99999999");
}

static UniValue ValueFromString(const std::string &str)
{
    UniValue value;
    BOOST_CHECK(value.setNumStr(str));
    return value;
}

BOOST_AUTO_TEST_CASE(rpc_parse_monetary_values)
{
    BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.00000001")), 1LL);
    BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.17622195")), 17622195LL);
    BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.5")), 50000000LL);
    BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.50000000")), 50000000LL);
    BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.89898989")), 89898989LL);
    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_AUTO_TEST_CASE(json_parse_errors)
{
    Value value;
    // Valid
    BOOST_CHECK_EQUAL(read_string(std::string("1.0"), value), true);
    // Valid, with trailing whitespace
    BOOST_CHECK_EQUAL(read_string(std::string("1.0 "), value), true);
    // Invalid, initial garbage
    BOOST_CHECK_EQUAL(read_string(std::string("[1.0"), value), false);
    BOOST_CHECK_EQUAL(read_string(std::string("a1.0"), value), false);
    // Invalid, trailing garbage
    BOOST_CHECK_EQUAL(read_string(std::string("1.0sds"), value), false);
    BOOST_CHECK_EQUAL(read_string(std::string("1.0]"), value), false);
    // BTC addresses should fail parsing
    BOOST_CHECK_EQUAL(read_string(std::string("175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W"), value), false);
    BOOST_CHECK_EQUAL(read_string(std::string("3J98t1WpEZ73CNmQviecrnyiWrnqRhWNL"), value), false);
}

BOOST_AUTO_TEST_CASE(rpc_boostasiotocnetaddr)
{
    // Check IPv4 addresses
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("1.2.3.4")).ToString(), "1.2.3.4");
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("127.0.0.1")).ToString(), "127.0.0.1");
    // Check IPv6 addresses
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::1")).ToString(), "::1");
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("123:4567:89ab:cdef:123:4567:89ab:cdef")).ToString(),
                                         "123:4567:89ab:cdef:123:4567:89ab:cdef");
    // v4 compatible must be interpreted as IPv4
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::0:127.0.0.1")).ToString(), "127.0.0.1");
    // v4 mapped must be interpreted as IPv4
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::ffff:127.0.0.1")).ToString(), "127.0.0.1");
}

BOOST_AUTO_TEST_SUITE_END()
<commit_msg>fix univalue json parse tests<commit_after>// Copyright (c) 2012-2013 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "rpcserver.h"
#include "rpcclient.h"

#include "base58.h"
#include "netbase.h"

#include "test/test_bitcoin.h"

#include <boost/algorithm/string.hpp>
#include <boost/test/unit_test.hpp>

#include "univalue/univalue.h"

using namespace std;

UniValue
createArgs(int nRequired, const char* address1=NULL, const char* address2=NULL)
{
    UniValue result(UniValue::VARR);
    result.push_back(nRequired);
    UniValue addresses(UniValue::VARR);
    if (address1) addresses.push_back(address1);
    if (address2) addresses.push_back(address2);
    result.push_back(addresses);
    return result;
}

UniValue CallRPC(string args)
{
    vector<string> vArgs;
    boost::split(vArgs, args, boost::is_any_of(" \t"));
    string strMethod = vArgs[0];
    vArgs.erase(vArgs.begin());
    UniValue params = RPCConvertValues(strMethod, vArgs);

    rpcfn_type method = tableRPC[strMethod]->actor;
    try {
        UniValue result = (*method)(params, false);
        return result;
    }
    catch (const UniValue& objError) {
        throw runtime_error(find_value(objError, "message").get_str());
    }
}

BOOST_FIXTURE_TEST_SUITE(rpc_tests, TestingSetup)

BOOST_AUTO_TEST_CASE(rpc_rawparams)
{
    // Test raw transaction API argument handling
    UniValue r;

    BOOST_CHECK_THROW(CallRPC("getrawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("getrawtransaction not_hex"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("getrawtransaction a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed not_int"), runtime_error);

    BOOST_CHECK_THROW(CallRPC("createrawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("createrawtransaction null null"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("createrawtransaction not_array"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("createrawtransaction [] []"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("createrawtransaction {} {}"), runtime_error);
    BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [] {}"));
    BOOST_CHECK_THROW(CallRPC("createrawtransaction [] {} extra"), runtime_error);

    BOOST_CHECK_THROW(CallRPC("decoderawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("decoderawtransaction null"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("decoderawtransaction DEADBEEF"), runtime_error);
    string rawtx = "0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000";
    BOOST_CHECK_NO_THROW(r = CallRPC(string("decoderawtransaction ")+rawtx));
    BOOST_CHECK_EQUAL(find_value(r.get_obj(), "version").get_int(), 1);
    BOOST_CHECK_EQUAL(find_value(r.get_obj(), "locktime").get_int(), 0);
    BOOST_CHECK_THROW(r = CallRPC(string("decoderawtransaction ")+rawtx+" extra"), runtime_error);

    BOOST_CHECK_THROW(CallRPC("signrawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("signrawtransaction null"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("signrawtransaction ff00"), runtime_error);
    BOOST_CHECK_NO_THROW(CallRPC(string("signrawtransaction ")+rawtx));
    BOOST_CHECK_NO_THROW(CallRPC(string("signrawtransaction ")+rawtx+" null null NONE|ANYONECANPAY"));
    BOOST_CHECK_NO_THROW(CallRPC(string("signrawtransaction ")+rawtx+" [] [] NONE|ANYONECANPAY"));
    BOOST_CHECK_THROW(CallRPC(string("signrawtransaction ")+rawtx+" null null badenum"), runtime_error);

    // Only check failure cases for sendrawtransaction, there's no network to send to...
    BOOST_CHECK_THROW(CallRPC("sendrawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("sendrawtransaction null"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("sendrawtransaction DEADBEEF"), runtime_error);
    BOOST_CHECK_THROW(CallRPC(string("sendrawtransaction ")+rawtx+" extra"), runtime_error);
}

BOOST_AUTO_TEST_CASE(rpc_rawsign)
{
    UniValue r;
    // input is a 1-of-2 multisig (so is output):
    string prevout =
      "[{\"txid\":\"b4cc287e58f87cdae59417329f710f3ecd75a4ee1d2872b7248f50977c8493f3\","
      "\"vout\":1,\"scriptPubKey\":\"a914b10c9df5f7edf436c697f02f1efdba4cf399615187\","
      "\"redeemScript\":\"512103debedc17b3df2badbcdd86d5feb4562b86fe182e5998abd8bcd4f122c6155b1b21027e940bb73ab8732bfdf7f9216ecefca5b94d6df834e77e108f68e66f126044c052ae\"}]";
    r = CallRPC(string("createrawtransaction ")+prevout+" "+
      "{\"3HqAe9LtNBjnsfM4CyYaWTnvCaUYT7v4oZ\":11}");
    string notsigned = r.get_str();
    string privkey1 = "\"KzsXybp9jX64P5ekX1KUxRQ79Jht9uzW7LorgwE65i5rWACL6LQe\"";
    string privkey2 = "\"Kyhdf5LuKTRx4ge69ybABsiUAWjVRK4XGxAKk2FQLp2HjGMy87Z4\"";
    r = CallRPC(string("signrawtransaction ")+notsigned+" "+prevout+" "+"[]");
    BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == false);
    r = CallRPC(string("signrawtransaction ")+notsigned+" "+prevout+" "+"["+privkey1+","+privkey2+"]");
    BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == true);
}

BOOST_AUTO_TEST_CASE(rpc_format_monetary_values)
{
    BOOST_CHECK(ValueFromAmount(0LL).write() == "0.00000000");
    BOOST_CHECK(ValueFromAmount(1LL).write() == "0.00000001");
    BOOST_CHECK(ValueFromAmount(17622195LL).write() == "0.17622195");
    BOOST_CHECK(ValueFromAmount(50000000LL).write() == "0.50000000");
    BOOST_CHECK(ValueFromAmount(89898989LL).write() == "0.89898989");
    BOOST_CHECK(ValueFromAmount(100000000LL).write() == "1.00000000");
    BOOST_CHECK(ValueFromAmount(2099999999999990LL).write() == "20999999.99999990");
    BOOST_CHECK(ValueFromAmount(2099999999999999LL).write() == "20999999.99999999");
}

static UniValue ValueFromString(const std::string &str)
{
    UniValue value;
    BOOST_CHECK(value.setNumStr(str));
    return value;
}

BOOST_AUTO_TEST_CASE(rpc_parse_monetary_values)
{
    BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.00000001")), 1LL);
    BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.17622195")), 17622195LL);
    BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.5")), 50000000LL);
    BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.50000000")), 50000000LL);
    BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.89898989")), 89898989LL);
    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_AUTO_TEST_CASE(json_parse_errors)
{
    UniValue value;
    // Valid
    BOOST_CHECK_EQUAL(value.read(std::string("1.0")), false);
    BOOST_CHECK_EQUAL(value.read(std::string("[1.0]")), true);
    // Valid, with trailing whitespace
    BOOST_CHECK_EQUAL(value.read(std::string("1.0 ")), false);
    BOOST_CHECK_EQUAL(value.read(std::string("[1.0 ] ")), true);
    // Invalid, initial garbage
    BOOST_CHECK_EQUAL(value.read(std::string("[1.0")), false);
    BOOST_CHECK_EQUAL(value.read(std::string("[a1.0]")), false);
     BOOST_CHECK_EQUAL(value.read(std::string("[\"a1.0\"]")), true);
    // Invalid, trailing garbage
    BOOST_CHECK_EQUAL(value.read(std::string("1.0sds")), false);
    BOOST_CHECK_EQUAL(value.read(std::string("1.0]")), false);
    // BTC addresses should fail parsing
    BOOST_CHECK_EQUAL(value.read(std::string("175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W")), false);
    BOOST_CHECK_EQUAL(value.read(std::string("3J98t1WpEZ73CNmQviecrnyiWrnqRhWNL")), false);
}

BOOST_AUTO_TEST_CASE(rpc_boostasiotocnetaddr)
{
    // Check IPv4 addresses
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("1.2.3.4")).ToString(), "1.2.3.4");
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("127.0.0.1")).ToString(), "127.0.0.1");
    // Check IPv6 addresses
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::1")).ToString(), "::1");
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("123:4567:89ab:cdef:123:4567:89ab:cdef")).ToString(),
                                         "123:4567:89ab:cdef:123:4567:89ab:cdef");
    // v4 compatible must be interpreted as IPv4
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::0:127.0.0.1")).ToString(), "127.0.0.1");
    // v4 mapped must be interpreted as IPv4
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::ffff:127.0.0.1")).ToString(), "127.0.0.1");
}

BOOST_AUTO_TEST_SUITE_END()
<|endoftext|>


<commit_before>// Copyright (c) 2012-2013 The Bitcoin Core developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "rpcserver.h"
#include "rpcclient.h"

#include "base58.h"
#include "netbase.h"

#include <boost/algorithm/string.hpp>
#include <boost/test/unit_test.hpp>

using namespace std;
using namespace json_spirit;

Array
createArgs(int nRequired, const char* address1=NULL, const char* address2=NULL)
{
    Array result;
    result.push_back(nRequired);
    Array addresses;
    if (address1) addresses.push_back(address1);
    if (address2) addresses.push_back(address2);
    result.push_back(addresses);
    return result;
}

Value CallRPC(string args)
{
    vector<string> vArgs;
    boost::split(vArgs, args, boost::is_any_of(" \t"));
    string strMethod = vArgs[0];
    vArgs.erase(vArgs.begin());
    Array params = RPCConvertValues(strMethod, vArgs);

    rpcfn_type method = tableRPC[strMethod]->actor;
    try {
        Value result = (*method)(params, false);
        return result;
    }
    catch (Object& objError)
    {
        throw runtime_error(find_value(objError, "message").get_str());
    }
}

BOOST_AUTO_TEST_SUITE(rpc_tests)

BOOST_AUTO_TEST_CASE(rpc_rawparams)
{
    // Test raw transaction API argument handling
    Value r;

    BOOST_CHECK_THROW(CallRPC("getrawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("getrawtransaction not_hex"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("getrawtransaction a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed not_int"), runtime_error);

    BOOST_CHECK_THROW(CallRPC("createrawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("createrawtransaction null null"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("createrawtransaction not_array"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("createrawtransaction [] []"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("createrawtransaction {} {}"), runtime_error);
    BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [] {}"));
    BOOST_CHECK_THROW(CallRPC("createrawtransaction [] {} extra"), runtime_error);

    BOOST_CHECK_THROW(CallRPC("decoderawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("decoderawtransaction null"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("decoderawtransaction DEADBEEF"), runtime_error);
    string rawtx = "0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000";
    BOOST_CHECK_NO_THROW(r = CallRPC(string("decoderawtransaction ")+rawtx));
    BOOST_CHECK_EQUAL(find_value(r.get_obj(), "version").get_int(), 1);
    BOOST_CHECK_EQUAL(find_value(r.get_obj(), "locktime").get_int(), 0);
    BOOST_CHECK_THROW(r = CallRPC(string("decoderawtransaction ")+rawtx+" extra"), runtime_error);

    BOOST_CHECK_THROW(CallRPC("signrawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("signrawtransaction null"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("signrawtransaction ff00"), runtime_error);
    BOOST_CHECK_NO_THROW(CallRPC(string("signrawtransaction ")+rawtx));
    BOOST_CHECK_NO_THROW(CallRPC(string("signrawtransaction ")+rawtx+" null null NONE|ANYONECANPAY"));
    BOOST_CHECK_NO_THROW(CallRPC(string("signrawtransaction ")+rawtx+" [] [] NONE|ANYONECANPAY"));
    BOOST_CHECK_THROW(CallRPC(string("signrawtransaction ")+rawtx+" null null badenum"), runtime_error);

    // Only check failure cases for sendrawtransaction, there's no network to send to...
    BOOST_CHECK_THROW(CallRPC("sendrawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("sendrawtransaction null"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("sendrawtransaction DEADBEEF"), runtime_error);
    BOOST_CHECK_THROW(CallRPC(string("sendrawtransaction ")+rawtx+" extra"), runtime_error);
}

BOOST_AUTO_TEST_CASE(rpc_rawsign)
{
    Value r;
    // input is a 1-of-2 multisig (so is output):

    string prevout =
      "[{\"txid\":\"fa0e4bbd829d23695accaacc63e9fbeeaa44c0bd60a15fd730597a35f83bed54\","
      "\"vout\":1,\"scriptPubKey\":\"a914ae20ad2a36715ceb9a4d108855f43739d57bf5ff87\","
      "\"redeemScript\":\"522102aa2b9ccf32a31e9dc02dfd609492db92cd6bbf60ac051dddd83bc2ef23835235210204dfd2dd68a82c091ee17e4cbf0b4aade7f4af16ac36bfc86ab059fbbd965d7152ae\"}]";
    r = CallRPC(string("createrawtransaction ")+prevout+" "+
      "{\"A8JyPAauw5YtNaBshKgVjPGyAqt7xv17Ad\":18}");
    string notsigned = r.get_str();
    string privkey1 = "\"QVLhi74xEKV5Y3JEyMcst7GSGBccgJmuawgjQxGHxjgjXqG6AaR9\"";
    string privkey2 = "\"QWQbSTCKxQb1e7KHuMv88udFTu88tvh1A71h44CC1SLa9hVVGEBT\"";
    r = CallRPC(string("signrawtransaction ")+notsigned+" "+prevout+" "+"[]");
    BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == false);
    r = CallRPC(string("signrawtransaction ")+notsigned+" "+prevout+" "+"["+privkey1+","+privkey2+"]");
    BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == true);
}

BOOST_AUTO_TEST_CASE(rpc_format_monetary_values)
{
    BOOST_CHECK(write_string(ValueFromAmount(0LL), false) == "0.00000000");
    BOOST_CHECK(write_string(ValueFromAmount(1LL), false) == "0.00000001");
    BOOST_CHECK(write_string(ValueFromAmount(17622195LL), false) == "0.17622195");
    BOOST_CHECK(write_string(ValueFromAmount(50000000LL), false) == "0.50000000");
    BOOST_CHECK(write_string(ValueFromAmount(89898989LL), false) == "0.89898989");
    BOOST_CHECK(write_string(ValueFromAmount(100000000LL), false) == "1.00000000");
    BOOST_CHECK(write_string(ValueFromAmount(2099999999999990LL), false) == "20999999.99999990");
    BOOST_CHECK(write_string(ValueFromAmount(2099999999999999LL), false) == "20999999.99999999");
}

static Value ValueFromString(const std::string &str)
{
    Value value;
    BOOST_CHECK(read_string(str, value));
    return value;
}

BOOST_AUTO_TEST_CASE(rpc_parse_monetary_values)
{
    BOOST_CHECK(AmountFromValue(ValueFromString("0.00000001")) == 1LL);
    BOOST_CHECK(AmountFromValue(ValueFromString("0.17622195")) == 17622195LL);
    BOOST_CHECK(AmountFromValue(ValueFromString("0.5")) == 50000000LL);
    BOOST_CHECK(AmountFromValue(ValueFromString("0.50000000")) == 50000000LL);
    BOOST_CHECK(AmountFromValue(ValueFromString("0.89898989")) == 89898989LL);
    BOOST_CHECK(AmountFromValue(ValueFromString("1.00000000")) == 100000000LL);
    BOOST_CHECK(AmountFromValue(ValueFromString("20999999.9999999")) == 2099999999999990LL);
    BOOST_CHECK(AmountFromValue(ValueFromString("20999999.99999999")) == 2099999999999999LL);
}

BOOST_AUTO_TEST_CASE(rpc_boostasiotocnetaddr)
{
    // Check IPv4 addresses
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("1.2.3.4")).ToString(), "1.2.3.4");
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("127.0.0.1")).ToString(), "127.0.0.1");
    // Check IPv6 addresses
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::1")).ToString(), "::1");
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("123:4567:89ab:cdef:123:4567:89ab:cdef")).ToString(),
                                         "123:4567:89ab:cdef:123:4567:89ab:cdef");
    // v4 compatible must be interpreted as IPv4
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::0:127.0.0.1")).ToString(), "127.0.0.1");
    // v4 mapped must be interpreted as IPv4
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::ffff:127.0.0.1")).ToString(), "127.0.0.1");
}

BOOST_AUTO_TEST_SUITE_END()
<commit_msg>rpc_tests: use BOOST_CHECK_EQUAL<commit_after>// Copyright (c) 2012-2013 The Bitcoin Core developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "rpcserver.h"
#include "rpcclient.h"

#include "base58.h"
#include "netbase.h"

#include <boost/algorithm/string.hpp>
#include <boost/test/unit_test.hpp>

using namespace std;
using namespace json_spirit;

Array
createArgs(int nRequired, const char* address1=NULL, const char* address2=NULL)
{
    Array result;
    result.push_back(nRequired);
    Array addresses;
    if (address1) addresses.push_back(address1);
    if (address2) addresses.push_back(address2);
    result.push_back(addresses);
    return result;
}

Value CallRPC(string args)
{
    vector<string> vArgs;
    boost::split(vArgs, args, boost::is_any_of(" \t"));
    string strMethod = vArgs[0];
    vArgs.erase(vArgs.begin());
    Array params = RPCConvertValues(strMethod, vArgs);

    rpcfn_type method = tableRPC[strMethod]->actor;
    try {
        Value result = (*method)(params, false);
        return result;
    }
    catch (Object& objError)
    {
        throw runtime_error(find_value(objError, "message").get_str());
    }
}

BOOST_AUTO_TEST_SUITE(rpc_tests)

BOOST_AUTO_TEST_CASE(rpc_rawparams)
{
    // Test raw transaction API argument handling
    Value r;

    BOOST_CHECK_THROW(CallRPC("getrawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("getrawtransaction not_hex"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("getrawtransaction a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed not_int"), runtime_error);

    BOOST_CHECK_THROW(CallRPC("createrawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("createrawtransaction null null"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("createrawtransaction not_array"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("createrawtransaction [] []"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("createrawtransaction {} {}"), runtime_error);
    BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [] {}"));
    BOOST_CHECK_THROW(CallRPC("createrawtransaction [] {} extra"), runtime_error);

    BOOST_CHECK_THROW(CallRPC("decoderawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("decoderawtransaction null"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("decoderawtransaction DEADBEEF"), runtime_error);
    string rawtx = "0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000";
    BOOST_CHECK_NO_THROW(r = CallRPC(string("decoderawtransaction ")+rawtx));
    BOOST_CHECK_EQUAL(find_value(r.get_obj(), "version").get_int(), 1);
    BOOST_CHECK_EQUAL(find_value(r.get_obj(), "locktime").get_int(), 0);
    BOOST_CHECK_THROW(r = CallRPC(string("decoderawtransaction ")+rawtx+" extra"), runtime_error);

    BOOST_CHECK_THROW(CallRPC("signrawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("signrawtransaction null"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("signrawtransaction ff00"), runtime_error);
    BOOST_CHECK_NO_THROW(CallRPC(string("signrawtransaction ")+rawtx));
    BOOST_CHECK_NO_THROW(CallRPC(string("signrawtransaction ")+rawtx+" null null NONE|ANYONECANPAY"));
    BOOST_CHECK_NO_THROW(CallRPC(string("signrawtransaction ")+rawtx+" [] [] NONE|ANYONECANPAY"));
    BOOST_CHECK_THROW(CallRPC(string("signrawtransaction ")+rawtx+" null null badenum"), runtime_error);

    // Only check failure cases for sendrawtransaction, there's no network to send to...
    BOOST_CHECK_THROW(CallRPC("sendrawtransaction"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("sendrawtransaction null"), runtime_error);
    BOOST_CHECK_THROW(CallRPC("sendrawtransaction DEADBEEF"), runtime_error);
    BOOST_CHECK_THROW(CallRPC(string("sendrawtransaction ")+rawtx+" extra"), runtime_error);
}

BOOST_AUTO_TEST_CASE(rpc_rawsign)
{
    Value r;
    // input is a 1-of-2 multisig (so is output):

    string prevout =
      "[{\"txid\":\"fa0e4bbd829d23695accaacc63e9fbeeaa44c0bd60a15fd730597a35f83bed54\","
      "\"vout\":1,\"scriptPubKey\":\"a914ae20ad2a36715ceb9a4d108855f43739d57bf5ff87\","
      "\"redeemScript\":\"522102aa2b9ccf32a31e9dc02dfd609492db92cd6bbf60ac051dddd83bc2ef23835235210204dfd2dd68a82c091ee17e4cbf0b4aade7f4af16ac36bfc86ab059fbbd965d7152ae\"}]";
    r = CallRPC(string("createrawtransaction ")+prevout+" "+
      "{\"A8JyPAauw5YtNaBshKgVjPGyAqt7xv17Ad\":18}");
    string notsigned = r.get_str();
    string privkey1 = "\"QVLhi74xEKV5Y3JEyMcst7GSGBccgJmuawgjQxGHxjgjXqG6AaR9\"";
    string privkey2 = "\"QWQbSTCKxQb1e7KHuMv88udFTu88tvh1A71h44CC1SLa9hVVGEBT\"";
    r = CallRPC(string("signrawtransaction ")+notsigned+" "+prevout+" "+"[]");
    BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == false);
    r = CallRPC(string("signrawtransaction ")+notsigned+" "+prevout+" "+"["+privkey1+","+privkey2+"]");
    BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == true);
}

BOOST_AUTO_TEST_CASE(rpc_format_monetary_values)
{
    BOOST_CHECK_EQUAL(write_string(ValueFromAmount(0LL), false), "0.00000000");
    BOOST_CHECK_EQUAL(write_string(ValueFromAmount(1LL), false), "0.00000001");
    BOOST_CHECK_EQUAL(write_string(ValueFromAmount(17622195LL), false), "0.17622195");
    BOOST_CHECK_EQUAL(write_string(ValueFromAmount(50000000LL), false), "0.50000000");
    BOOST_CHECK_EQUAL(write_string(ValueFromAmount(89898989LL), false), "0.89898989");
    BOOST_CHECK_EQUAL(write_string(ValueFromAmount(100000000LL), false), "1.00000000");
    BOOST_CHECK_EQUAL(write_string(ValueFromAmount(2099999999999990LL), false), "20999999.99999990");
    BOOST_CHECK_EQUAL(write_string(ValueFromAmount(2099999999999999LL), false), "20999999.99999999");
}

static Value ValueFromString(const std::string &str)
{
    Value value;
    BOOST_CHECK(read_string(str, value));
    return value;
}

BOOST_AUTO_TEST_CASE(rpc_parse_monetary_values)
{
    BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.00000001")), 1LL);
    BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.17622195")), 17622195LL);
    BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.5")), 50000000LL);
    BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.50000000")), 50000000LL);
    BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.89898989")), 89898989LL);
    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_AUTO_TEST_CASE(rpc_boostasiotocnetaddr)
{
    // Check IPv4 addresses
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("1.2.3.4")).ToString(), "1.2.3.4");
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("127.0.0.1")).ToString(), "127.0.0.1");
    // Check IPv6 addresses
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::1")).ToString(), "::1");
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("123:4567:89ab:cdef:123:4567:89ab:cdef")).ToString(),
                                         "123:4567:89ab:cdef:123:4567:89ab:cdef");
    // v4 compatible must be interpreted as IPv4
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::0:127.0.0.1")).ToString(), "127.0.0.1");
    // v4 mapped must be interpreted as IPv4
    BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::ffff:127.0.0.1")).ToString(), "127.0.0.1");
}

BOOST_AUTO_TEST_SUITE_END()
<|endoftext|>