text:
/*
 * This file is part of the DUDS project. It is subject to the BSD-style
 * license terms in the LICENSE file found in the top-level directory of this
 * distribution and at http://www.somewhere.org/somepath/license.html.
 * No part of DUDS, including this file, may be copied, modified, propagated,
 * or distributed except according to the terms contained in the LICENSE file.
 *
 * Copyright (C) 2020  Jeff Jackowski
 */
/**
 * @file
 * A demonstration of using the menu system with the bit-per-pixel renderer.
 */

#include <duds/hardware/devices/displays/SimulatedBppDisplay.hpp>
#include <duds/hardware/devices/displays/ST7920.hpp>
#include <duds/hardware/interface/linux/GpioDevPort.hpp>
#include <duds/hardware/interface/PinConfiguration.hpp>
#include <boost/property_tree/info_parser.hpp>
#include <duds/ui/graphics/BppImageArchive.hpp>
#include <duds/ui/menu/MenuAccess.hpp>
#include <duds/ui/menu/renderers/BppIconItem.hpp>
#include <duds/ui/menu/renderers/BppMenuRenderer.hpp>
#include <duds/ui/PathStringGenerator.hpp>
#include <iostream>
#include <chrono>
#include <thread>
#include <assert.h>
#include <boost/exception/diagnostic_information.hpp>
#include <boost/exception/errinfo_errno.hpp>
#include <boost/program_options.hpp>
#include <csignal>
#include <duds/os/linux/EvdevErrors.hpp>
#include <duds/os/linux/EvdevInput.hpp>
#include <assert.h>

std::sig_atomic_t quit = 0;

void signalHandler(int) {
	quit = 1;
}

// Doxygen 1.8.16 can find typedefs from these shortened namespaces, but not
// classes. In a few spots, some classes are referenced by the complete
// namespace so that Doxygen will list this source file as an example in the
// class documentation.
namespace display = duds::hardware::display;
namespace graphics = duds::ui::graphics;
namespace menu = duds::ui::menu;
namespace ui = duds::ui;
namespace os = duds::os::linux;

duds::ui::graphics::BppImageArchive menuicons;

class MenuViews;

void toggleVisibility(
	duds::ui::menu::GenericMenuItem &toggle,
	duds::ui::menu::MenuItemSptr vischange
) {
	vischange->changeVisibility(toggle.isToggledOn());
}

void toggleState(
	duds::ui::menu::GenericMenuItem &toggle
) {
	toggle.toggle();
}

/**
 * Handles the Menu objects so that only one copy of each will be needed.
 */
class Menus {
	menu::MenuSptr root;
	menu::MenuSptr subs[16][16];
	void makeRootMenuItem(menu::MenuAccess &ma);
	void makeSubMenuItem(
		menu::MenuAccess &ma,
		const graphics::BppFontSptr &iconFont,
		int x,
		int y
	);
	void makeBackMenuItem(menu::MenuAccess &ma);
public:
	Menus(const graphics::BppFontSptr &iconFont) {
		// first, make all the menu ojbects
		root = duds::ui::menu::Menu::make("Root");
		for (int l0 = 0; l0 < 16; ++l0) {
			for (int l1 = 0; l1 < 16; ++l1) {
				std::ostringstream oss;
				oss << "Sub " << l0 << '-' << l1;
				subs[l0][l1] = menu::Menu::make(oss.str());
			}
		}
		// next, make items for the menus
		{ // root
			duds::ui::menu::MenuAccess ma(root);
			for (int i = 0; i < 8; ++i) {
				makeSubMenuItem(ma, iconFont, i, 0);
			}
			{ // invisible item & toggle control
				menu::GenericMenuItemSptr invis = menu::GenericMenuItem::make(
					"Initially invisible",
					menu::MenuItem::Invisible
				);
				menu::GenericMenuItemSptr indis = menu::GenericMenuItem::make(
					"Disabled",
					menu::MenuItem::Invisible | menu::MenuItem::Disabled
				);
				menu::GenericMenuItemSptr intog = menu::GenericMenuItem::make(
					"Another toggle",
					menu::MenuItem::Invisible | menu::MenuItem::Toggle |
					menu::MenuItem::ToggledOn
				);
				menu::GenericMenuItemSptr tog = menu::GenericMenuItem::make(
					"Show invisible", menu::MenuItem::Toggle
				);
				tog->choseConnect(std::bind(
					&menu::MenuItem::toggle, std::placeholders::_3
				));
				tog->choseConnect(std::bind(
					&toggleVisibility, std::placeholders::_3, invis
				));
				tog->choseConnect(std::bind(
					&toggleVisibility, std::placeholders::_3, indis
				));
				tog->choseConnect(std::bind(
					&toggleVisibility, std::placeholders::_3, intog
				));
				intog->choseConnect(std::bind(
					&menu::MenuItem::toggle, std::placeholders::_3
				));
				ma.append(std::move(tog));
				ma.append(std::move(invis));
				ma.append(std::move(indis));
				ma.append(std::move(intog));
			}
			// long item; test clipping on right side
			ma.append(menu::GenericMenuItem::make(
				"Does nothing - 0123456789 - really long"
			));
		}
		// sub menus
		for (int l0 = 0; l0 < 16; ++l0) {
			for (int l1 = 0; l1 < 16; ++l1) {
				menu::MenuAccess ma(subs[l0][l1]);
				std::ostringstream oss;
				if (l1 < 15) {
					makeSubMenuItem(ma, iconFont, l0, l1 + 1);
				}
				if (l1 > 0) {
					makeSubMenuItem(ma, iconFont, l0, l1 - 1);
				}
				if (l0 < 15) {
					makeSubMenuItem(ma, iconFont, l0 + 1, l1);
				}
				if (l0 > 0) {
					makeSubMenuItem(ma, iconFont, l0 - 1, l1);
				}
				makeBackMenuItem(ma);
				makeRootMenuItem(ma);
			}
		}
	}
	const menu::MenuSptr &rootMenu() const {
		return root;
	}
	const menu::MenuSptr &subMenu(int x, int y) const {
		return subs[x][y];
	}
};

/**
 * Handles a copy of MenuView objects and attaches them to the menus in Menus.
 * There can be multiple MenuViews objects.
 */
class MenuViews {
	menu::MenuViewSptr root;
	menu::MenuViewSptr subs[16][16];
	menu::MenuViewSptr curr;
	graphics::BppStringCacheSptr strcache;
	duds::os::linux::InputHandlersSptr inputHandlers;
public:
	duds::ui::menu::renderers::BppMenuRenderer renderer;
	duds::ui::Path path;
	duds::ui::PathStringGenerator psgen;
private:
	void setMenu() {
		menu::MenuViewSptr view = std::static_pointer_cast<duds::ui::menu::MenuView>(
			path.currentPage()
		);
		if (view) {
			curr = view;
		}
		//std::cout << "Path: size = " << path.size() << ", " <<
		//psgen.generate(path) << "            " << std::endl;
	}
public:
	MenuViews(
		const Menus &menus,
		const graphics::BppStringCacheSptr &sc,
		const os::EvdevInputSptr input
	) :
		renderer(strcache, menu::renderers::BppMenuRenderer::InvertSelected),
		strcache(sc),
		psgen("/", ">")
	{
		renderer.toggledOffIcon(menuicons.get("Unmarked8x8"));
		renderer.toggledOnIcon(menuicons.get("Checked8x8"));
		// not really needed with inverted selection rendering
		//renderer.selectedIcon(menuicons.get("SelectTriangle"));
		renderer.disabledIcon(menuicons.get("Disabled8x8"));
		renderer.iconDimensions(strcache->font()->estimatedMaxCharacterSize());
		if (renderer.iconDimensions().w > 0) {
			renderer.iconTextMargin(1);
		}
		renderer.addScrollBar(2);
		root = curr = menu::MenuView::make(menus.rootMenu());
		root->context() = this;
		path.push(root);
		for (int l0 = 0; l0 < 16; ++l0) {
			for (int l1 = 0; l1 < 16; ++l1) {
				subs[l0][l1] = duds::ui::menu::MenuView::make(menus.subMenu(l0, l1));
				subs[l0][l1]->context() = this;
			}
		}
		psgen.currentHeader("[");
		psgen.currentFooter("]");
		//configure input
		if (input) {
			inputHandlers = input->makeConnectedHandlers();
			inputHandlers->connect(
				duds::os::linux::EventTypeCode(EV_KEY, KEY_UP),
				std::bind(&MenuViews::inputUp, this, std::placeholders::_2)
			);
			inputHandlers->connect(
				os::EventTypeCode(EV_KEY, KEY_PAGEUP),
				std::bind(&MenuViews::inputPageUp, this, std::placeholders::_2)
			);
			inputHandlers->connect(
				os::EventTypeCode(EV_KEY, KEY_DOWN),
				std::bind(&MenuViews::inputDown, this, std::placeholders::_2)
			);
			inputHandlers->connect(
				os::EventTypeCode(EV_KEY, KEY_PAGEDOWN),
				std::bind(&MenuViews::inputPageDown, this, std::placeholders::_2)
			);
			inputHandlers->connect(
				os::EventTypeCode(EV_KEY, KEY_HOME),
				std::bind(&MenuViews::inputHome, this, std::placeholders::_2)
			);
			inputHandlers->connect(
				os::EventTypeCode(EV_KEY, KEY_END),
			std::bind(&MenuViews::inputEnd, this, std::placeholders::_2)
			);
			inputHandlers->connect(
				os::EventTypeCode(EV_KEY, KEY_ENTER),
				std::bind(&MenuViews::inputChose, this, std::placeholders::_2)
			);
			inputHandlers->connect(
				os::EventTypeCode(EV_KEY, KEY_LEFT),
				std::bind(&MenuViews::inputBack, this, std::placeholders::_2)
			);
			inputHandlers->connect(
				os::EventTypeCode(EV_KEY, KEY_RIGHT),
				std::bind(&MenuViews::inputForward, this, std::placeholders::_2)
			);
			inputHandlers->connect(
				os::EventTypeCode(EV_KEY, KEY_ESC),
				std::bind(&signalHandler, 0)
			);
		}
	}
	void inputUp(int val) {
		if (val > 0) {
			curr->forward(val);
		}
	}
	void inputPageUp(int val) {
		if (val > 0) {
			curr->forward(renderer.maxVisible());
		}
	}
	void inputDown(int val) {
		if (val > 0) {
			curr->backward(val);
		}
	}
	void inputPageDown(int val) {
		if (val > 0) {
			curr->backward(renderer.maxVisible());
		}
	}
	void inputHome(int val) {
		if (val > 0) {
			curr->jumpToFirst();
		}
	}
	void inputEnd(int val) {
		if (val > 0) {
			curr->jumpToLast();
		}
	}
	void inputChose(int val) {
		if (val > 0) {
			curr->chose();
		}
	}
	void inputBack(int val) {
		if (val > 0) {
			back();
		}
	}
	void inputForward(int val) {
		if (val > 0) {
			forward();
		}
	}
	void back() {
		path.back();
		setMenu();
	}
	void forward() {
		path.forward();
		setMenu();
	}
	void changePage(const ui::PageSptr &nextpage) {
		path.push(nextpage);
		setMenu();
	}
	void changePage(int x, int y) {
		path.push(subs[x][y]);
		setMenu();
	}
	void changeToRoot() {
		path.push(root);
		setMenu();
	}
	const graphics::BppStringCacheSptr &stringCache() const {
		return strcache;
	}
	std::string pathString() const {
		return psgen.generate(path);
	}
	const menu::MenuViewSptr &rootView() const {
		return root;
	}
	const menu::MenuViewSptr &subView(int x, int y) const {
		return subs[x][y];
	}
	const menu::MenuViewSptr &view() const {
		return curr;
	}
};

// -----
// Implementations for functions in Menus that use MenuViews

MenuViews *GetMenuView(menu::MenuView &view) {
	return boost::any_cast<MenuViews*>(view.context());
}

void Menus::makeRootMenuItem(menu::MenuAccess &ma) {
	menu::GenericMenuItemSptr gmi = menu::GenericMenuItem::make("Root");
	gmi->choseConnect(std::bind(
		&MenuViews::changeToRoot,
		std::bind(&GetMenuView, std::placeholders::_1)
	));
	ma.append(std::move(gmi));
}

void Menus::makeSubMenuItem(
	menu::MenuAccess &ma,
	const graphics::BppFontSptr &iconFont,
	int x,
	int y
) {
	std::ostringstream oss;
	oss << "Goto Sub " << x << '-' << y;
	duds::ui::menu::renderers::GenericBppMenuIconItemSptr gmi =
		duds::ui::menu::renderers::GenericBppMenuIconItem::make(oss.str());
	// use char vals 1-31 inclusive
	gmi->icon(iconFont->tryGet((x + y * 4) % 30 + 1));
	gmi->choseConnect(std::bind(
		(void(MenuViews::*)(int,int))&MenuViews::changePage,
		std::bind(&GetMenuView, std::placeholders::_1),
		x,
		y
	));
	ma.append(std::move(gmi));
}

void Menus::makeBackMenuItem(menu::MenuAccess &ma) {
	menu::GenericMenuItemSptr gmi = menu::GenericMenuItem::make("Back");
	gmi->choseConnect(std::bind(
		&MenuViews::back,
		std::bind(&GetMenuView, std::placeholders::_1)
	));
	ma.append(std::move(gmi));
}

void runtest(
	display::BppGraphicDisplaySptr disp,
	const graphics::BppStringCacheSptr &tcache,
	MenuViews &views,
	const os::EvdevInputSptr &input
) try {
	graphics::BppImage frame(disp->dimensions());
	frame.clearImage();
	graphics::BppImageSptr menuimg;
	menu::MenuOutput menuout;
	graphics::ImageLocation mdest;
	int theight;
	{
		graphics::ImageDimensions tdim =
			tcache->font()->estimatedMaxCharacterSize();
		theight = tdim.h;
		mdest.x = 0;
		mdest.y = tdim.h + 1;
		graphics::ImageDimensions cdim =
			views.stringCache()->font()->estimatedMaxCharacterSize();
		int lines = (disp->height() - mdest.y) / cdim.h + 1;
		menuout.attach(views.rootView(), lines);
		views.renderer.maxVisible(lines);
		// may restrict length with variable width font, so use double width
		views.psgen.maxLength((disp->width() * 2) / tdim.w);
		frame.invertLines(tdim.h, 1);
		menuimg = graphics::BppImage::make(disp->width(), disp->height() - mdest.y);
	}
	int cnt = 0;
	while ((input || (++cnt < 48)) && !quit) {
		{ // render the menu
			menu::MenuOutputAccess moa(menuout);
			if (moa.changed()) {
				views.renderer.render(menuimg, moa);
				frame.write(menuimg, mdest);
				//label = moa.selectedItem().label();
				// render title (path)
				frame.clearLines(0, theight);
				graphics::ConstBppImageSptr title = tcache->text(views.pathString());
				// work out right side of title when it is too long to fit
				graphics::ImageLocation titleSrc(0, 0);
				graphics::ImageDimensions titleDim = title->dimensions();
				if (titleDim.w > frame.width()) {
					titleSrc.x = titleDim.w - frame.width();
					titleDim.w = frame.width();
				}
				// write the title, right justified if it doesn't fit
				frame.write(
					title,
					graphics::ImageLocation(0, 0),
					titleSrc,
					titleDim
				);
				disp->write(&frame);
			}
		}

		// must partially move this out to handle multiple displays
		if (input) {
			std::this_thread::sleep_for(std::chrono::milliseconds(32));
			views.view()->update();
			// only needed when visible menu changes
			menuout.attach(views.view());
		} else {
			std::this_thread::sleep_for(std::chrono::seconds(2));
			//views.view()->jumpToLast();
			//views.view()->backward(3);
			//views.view()->update();
			if ((cnt & 15) == 8) {
				views.back();
				menuout.attach(views.view());
				views.view()->forward();
				views.view()->update();
			} else if (cnt & 1) {
				views.view()->chose();
				views.view()->update();
				menuout.attach(views.view());
			} else {
				views.view()->backward(2);
				views.view()->update();
			}
		}
	}
	quit = true;
} catch (...) {
	std::cerr << "Test failed in runtest():\n" <<
	boost::current_exception_diagnostic_information()
	<< std::endl;
	quit = true;
}

void doPoll(os::Poller &poller)
try {
	while (!quit) {
		// wait time is how long it may take this thread to end, and it must
		// end before program termination
		poller.wait(std::chrono::milliseconds(64));
	}
} catch (...) {
	std::cerr << "Test failed in doPoll():\n" <<
	boost::current_exception_diagnostic_information() << std::endl;
	quit = true;
}

int main(int argc, char *argv[])
try {
	std::string devpath, mfontpath, tfontpath, miconpath, confpath, lcdname;
	int dispW, dispH;
	bool grabinput = false, uselcd = false;
	std::string imgpath(argv[0]);
	{
		int found = 0;
		while (!imgpath.empty() && (found < 3)) {
			imgpath.pop_back();
			if (imgpath.back() == '/') {
				++found;
			}
		}
		imgpath += "images/";
	}
	{ // option parsing
		boost::program_options::options_description optdesc(
			"Options for bit-per-pixel menu test"
		);
		optdesc.add_options()
			( // help info
				"help,h",
				"Show this help message"
			)
			( // the display width
				"width,x",
				boost::program_options::value<int>(&dispW)->
					default_value(144),
				"Display width in pixels"
			)
			( // the display height
				"height,y",
				boost::program_options::value<int>(&dispH)->
					default_value(32),
				"Display height in pixels"
			)
			(
				"input,i",
				boost::program_options::value<std::string>(&devpath),
					//->default_value("/dev/input/event0"),
				"Input device path, typically /dev/input/event[0-9]+. If "
				"unspecified, pre-programmed input will be used."
			)
			( // request exclusive access to input
				"grab,g",
				"Request exclusive access to the input device. Intended to "
				"prevent input from showing up on the same console that has this "
				"program's output."
			)
			(
				"tfont",
				boost::program_options::value<std::string>(&tfontpath)->
					default_value(imgpath + "font_Vx7.bppia"),
				"Title font file"
			)
			(
				"mfont",
				boost::program_options::value<std::string>(&mfontpath)->
					default_value(imgpath + "font_Vx8B.bppia"),
				"Menu font file"
			)
			(
				"icons",
				boost::program_options::value<std::string>(&miconpath)->
					default_value(imgpath + "menuicons.bppia"),
				"Menu icon image file"
			)
			(
				"st7920",
				"Use a graphic ST7920 LCD"
			)
			(
				"conf,c",
				boost::program_options::value<std::string>(&confpath)->
					default_value("samples/pins.conf"),
				"Pin configuration file; required if LCD used"
			)
			(
				"lcdname",
				boost::program_options::value<std::string>(&lcdname)->
					default_value("lcdGraphic"),
				"Name of LCD inside pin configuration"
			)
		;
		boost::program_options::variables_map vm;
		boost::program_options::store(
			boost::program_options::parse_command_line(argc, argv, optdesc),
			vm
		);
		boost::program_options::notify(vm);
		if (vm.count("help")) {
			std::cout << "Test of bit-per-pixel menu\n" <<
			argv[0] << " [options]\n" << optdesc << std::endl;
			return 0;
		}
		if (vm.count("grab")) {
			grabinput = true;
		}
		if (vm.count("st7920")) {
			uselcd = true;
		}
	}
	std::signal(SIGINT, &signalHandler);
	std::signal(SIGTERM, &signalHandler);

	// configure input
	duds::os::linux::Poller poller;
	duds::os::linux::EvdevInputSptr einput;
	std::thread inputPolling;
	if (!devpath.empty()) {
		try {
			einput = os::EvdevInput::make(devpath);
		} catch (duds::os::linux::EvdevFileOpenError &) {
			std::cerr << "Failed to open device file " << devpath << std::endl;
			return 2;
		}  catch (duds::os::linux::EvdevInitError &eie) {
			std::cerr << "Failed to initalize libevdev, error code " <<
			*boost::get_error_info<boost::errinfo_errno>(eie) << std::endl;
			return 3;
		}
		einput->usePoller(poller);
		inputPolling = std::thread(&doPoll, std::ref(poller));
		if (grabinput) {
			// wait until enter key released
			while (einput->value(os::EventTypeCode(EV_KEY, KEY_ENTER)) != 0) {
				std::this_thread::sleep_for(std::chrono::milliseconds(32));
			}
			if (!einput->grab()) {
				std::cerr << "Failed to grab input device." << std::endl;
			}
		}
		/*
		einput->connect(
			//os::EventTypeCode(EV_KEY, KEY_ESC),
			std::bind(&signalHandler, 0)
		);
		*/
	}

	// load fonts
	graphics::BppStringCacheSptr mfontCache(duds::ui::graphics::BppStringCache::make(
		graphics::BppFont::make(mfontpath)
	));
	graphics::BppStringCacheSptr tfontCache(graphics::BppStringCache::make(
		tfontpath
	));
	// load check icons
	menuicons.load(miconpath);

	{
		// display configuration
		display::BppGraphicDisplaySptr disp;
		duds::hardware::interface::PinConfiguration pc;
		std::shared_ptr<duds::hardware::interface::DigitalPort> port;
		// ST7920
		if (uselcd) {
			boost::property_tree::ptree tree;
			boost::property_tree::read_info(confpath, tree);
			// if an exception is thrown here, the program will terminate without
			// getting to the catch block below; don't know why
			pc.parse(tree.get_child("pins"));
			port = duds::hardware::interface::linux::GpioDevPort::makeConfiguredPort(pc);
			duds::hardware::interface::DigitalPinSet lcdset;
			duds::hardware::interface::ChipSelect lcdsel;
			pc.getPinSetAndSelect(lcdset, lcdsel, lcdname);
			std::shared_ptr<duds::hardware::devices::displays::ST7920> lcd =
				std::make_shared<duds::hardware::devices::displays::ST7920>(
					std::move(lcdset), std::move(lcdsel), dispW, dispH
				);
			lcd->initialize();
			disp = std::move(lcd);
		} else {
			// comsole output
			disp = std::make_shared<duds::hardware::devices::displays::SimulatedBppDisplay>(dispW, dispH);
		}

		// make the menus
		Menus menus(mfontCache->font());
		MenuViews views(menus, mfontCache, einput);

		runtest(disp, std::ref(tfontCache), std::ref(views), std::ref(einput));
	}

	std::cout << "Title font string cache image size: " << tfontCache->bytes() <<
	" bytes in " << tfontCache->strings() << " strings.\n"
	"Menu font string cache image size: " << mfontCache->bytes() << " bytes in " <<
	mfontCache->strings() << " strings." << std::endl;

	if (einput) {
		inputPolling.join();
	}
} catch (...) {
	quit = true;
	std::cerr << "Test failed in main():\n" <<
	boost::current_exception_diagnostic_information() << std::endl;
	return 1;
}

max_stars_repo_name: jjackowski/duds

max_issues_repo_name: jjackowski/duds

max_forks_repo_name: jjackowski/duds


text: #include <boost/compute/utility/program_cache.hpp>

ext: hpp

max_stars_repo_name: miathedev/BoostForArduino

max_issues_repo_name: miathedev/BoostForArduino

max_forks_repo_name: miathedev/BoostForArduino


text:
/*
 * scanner.cpp the main class for a block scanner.
 * @note the mode must be set appropriately
 * Use T1 to scan a tier 1 chain, T2 to scan a T2 chain, and scan to scan raw transactions.
 *
 * Scans and checks public keys without using any private keys.
 * Handles each file independently, so blockchains should be in a single file.
 *
 *  Created on: May 20, 2018
 *  Author: Nick Williams
 */

#include <string>
#include <iostream>
#include <fstream>
#include <functional>
#include <thread>

#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>

#include "common/devcash_context.h"
#include "modules/BlockchainModule.h"
#include "primitives/json_interface.h"
#include "primitives/block_tools.h"
#include "common/logger.h"

using namespace Devcash;
namespace fs = boost::filesystem;
namespace bpt = boost::property_tree;

struct scanner_options {
  eAppMode mode  = eAppMode::T1;
  std::string working_dir;
  std::string write_file;
  std::string address_filter;
  eDebugMode debug_mode = eDebugMode::off;
  unsigned int version = 0;
};

std::unique_ptr<struct scanner_options> ParseScannerOptions(int argc, char** argv);

/**
 * Convert the input string to a json ptree and add it to the supplied tree
 *
 * @param input_string Input JSON string containing FinalBlocks
 * @param tree Tree in which to add the new FinalBlock
 * @param block_count The block index number
 */
void add_to_tree(const std::string& input_string, bpt::ptree& tree, size_t block_count) {
  bpt::ptree pt2;
  std::istringstream is(input_string);
  bpt::read_json(is, pt2);
  std::string key = "block_" + std::to_string(block_count);
  //tree.put_child(key, pt2);
  tree.push_back(std::make_pair("", pt2));
}

/**
 * Filters the ptree (array of blocks) and returns a ptree with a list
 * of transactions that contain a transfer involving the provided
 * address
 *
 * @param tree A ptree of blocks to search in
 * @param filter_address The address to search for
 * @return New ptree containing list of matched addresses
 */
bpt::ptree filter_by_address(const bpt::ptree& tree, const std::string& filter_address) {
  bpt::ptree tx_tree;
  bpt::ptree tx_children;
  for (auto block : tree) {
    const bpt::ptree txs = block.second.get_child("txs");
    for (auto tx : txs) {
      const bpt::ptree transfers = tx.second.get_child("xfer");
      bool addr_found = false;
      for (auto xfer : transfers) {
        auto address = xfer.second.find("addr");
        std::string addr = address->second.data();
        std::size_t found = addr.find(filter_address);
        if (found != std::string::npos) {
          addr_found = true;
        }
      }
      if (addr_found) {
        tx_children.push_back(std::make_pair("", tx.second));
      }
    }
  }
  tx_tree.add_child("txs", tx_children);
  return tx_tree;
}

int main(int argc, char* argv[])
{
  init_log();

  CASH_TRY {
    auto options = ParseScannerOptions(argc, argv);

    if (!options) {
      exit(-1);
    }

    size_t block_counter = 0;
    size_t tx_counter = 0;
    size_t tfer_count = 0;
    size_t total_volume = 0;

    KeyRing keys;

    fs::path p(options->working_dir);
    if (p.empty()) {
      LOG_WARNING << "Invalid path: "+options->working_dir;
      return(false);
    }

    if (!is_directory(p)) {
      LOG_ERROR << "Error opening dir: " << options->working_dir << " is not a directory";
      return false;
    }

    bpt::ptree block_array;
    std::vector<std::string> files;
    for(auto& entry : boost::make_iterator_range(fs::directory_iterator(p), {})) {
      files.push_back(entry.path().string());
    }
    for  (auto const& file_name : files) {
      LOG_DEBUG << "Reading " << file_name;
      std::ifstream file(file_name, std::ios::binary);
      file.unsetf(std::ios::skipws);
      std::size_t file_size;
      file.seekg(0, std::ios::end);
      file_size = file.tellg();
      file.seekg(0, std::ios::beg);

      std::vector<byte> raw;
      raw.reserve(file_size);
      raw.insert(raw.begin(), std::istream_iterator<byte>(file), std::istream_iterator<byte>());
      bool is_block = IsBlockData(raw);
      bool is_transaction = IsTxData(raw);
      if (is_block) { LOG_INFO << file_name << " has blocks."; }
      if (is_transaction) { LOG_INFO << file_name << " has transactions."; }
      if (!is_block && !is_transaction) {
        LOG_WARNING << file_name << " contains unknown data.";
      }
      size_t file_blocks = 0;
      size_t file_txs = 0;
      size_t file_tfer = 0;
      uint64_t start_time = 0;
      uint64_t blocktime = 0;
      uint64_t volume = 0;
      uint64_t value = 0;

      ChainState priori;
      ChainState posteri;

      InputBuffer buffer(raw);
      while (buffer.getOffset() < static_cast<size_t>(file_size)) {
        try {
          if (is_transaction) {
            Tier2Transaction tx(Tier2Transaction::Create(buffer, keys, true));
            file_txs++;
            file_tfer += tx.getTransfers().size();

            add_to_tree(tx.getJSON(), block_array, block_counter);
          } else if (is_block) {
            FinalBlock one_block(buffer, priori, keys, options->mode);

            if (one_block.getVersion() != options->version) {
              LOG_WARNING << "Unexpected block version ("+std::to_string(one_block.getVersion())+") in " << file_name;
            }
            size_t txs_count = one_block.getNumTransactions();
            size_t tfers = one_block.getNumTransfers();
            uint64_t previous_time = blocktime;
            blocktime = one_block.getBlockTime();
            uint64_t duration = blocktime-previous_time;
            priori = one_block.getChainState();

            add_to_tree(GetJSON(one_block), block_array, block_counter);

            LOG_INFO << std::to_string(txs_count)+" txs, transfers: "+std::to_string(tfers);
            LOG_INFO << "Duration: "+std::to_string(duration)+" ms.";
            if (duration != 0 && previous_time != 0) {
              LOG_INFO << "Rate: "+std::to_string(txs_count*1000/duration)+" txs/sec";
            } else if (previous_time == 0) {
              start_time = blocktime;
            }
            uint64_t block_volume = one_block.getVolume();
            volume += block_volume;
            value += one_block.getValue();
            LOG_INFO << "Volume: "+std::to_string(block_volume);
            LOG_INFO << "Value: "+std::to_string(one_block.getValue());

            Summary block_summary(Summary::Create());
            std::vector<TransactionPtr> txs = one_block.CopyTransactions();
            for (TransactionPtr& item : txs) {
              if (!item->isValid(posteri, keys, block_summary)) {
                LOG_WARNING << "A transaction is invalid. TX details: ";
                LOG_WARNING << item->getJSON();
              }
            }
            if (block_summary.getCanonical() != one_block.getSummary().getCanonical()) {
              LOG_WARNING << "A final block summary is invalid. Summary datails: ";
              LOG_WARNING << GetJSON(one_block.getSummary());
              LOG_WARNING << "Transaction details: ";
              for (TransactionPtr& item : txs) {
                LOG_WARNING << item->getJSON();
              }
            }

            file_blocks++;
            file_txs += txs_count;
            file_tfer += tfers;
          } else {
            LOG_WARNING << "!is_block && !is_transaction";
          }
        } catch (const std::exception& e) {
          LOG_ERROR << "Error scanning " << file_name << " skipping to next file.  Error details: "+FormatException(&e, "scanner");
          LOG_ERROR << "Offset/Size: "+std::to_string(buffer.getOffset())+"/"+std::to_string(file_size);
          break;
        }
      }

      if (posteri.getStateMap().empty()) {
        LOG_DEBUG << "End with no chainstate.";
	  } else {
        LOG_DEBUG << "End chainstate: " + WriteChainStateMap(posteri.getStateMap());
	  }

      uint64_t duration = blocktime-start_time;
      if (duration != 0 && start_time != 0) {
        LOG_INFO << file_name << " overall rate: "+std::to_string(file_txs*1000/duration)+" txs/sec";
      }
      LOG_INFO << file_name << " has " << std::to_string(file_txs)
              << " txs, " +std::to_string(file_tfer)+" tfers in "+std::to_string(file_blocks)+" blocks.";
      LOG_INFO << file_name + " coin volume is "+std::to_string(volume);

      block_counter += file_blocks;
      tx_counter += file_txs;
      tfer_count += file_tfer;
      total_volume += volume;
    }
    LOG_INFO << "Dir has "+std::to_string(tx_counter)+" txs, "
      +std::to_string(tfer_count)+" tfers in "+std::to_string(block_counter)+" blocks.";
    LOG_INFO << "Grand total coin volume is "+std::to_string(total_volume);

    if (!options->write_file.empty()) {
      std::ofstream out_file(options->write_file, std::ios::out);
      if (out_file.is_open()) {
        std::stringstream json_tot;
        if (options->address_filter.size() > 0) {
          auto txs_tree = filter_by_address(block_array, options->address_filter);
          bpt::write_json(json_tot, txs_tree);
        } else {
          bpt::ptree block_ptree;
          block_ptree.add_child("blocks", block_array);
          bpt::write_json(json_tot, block_ptree);
        }
        out_file << json_tot.str();
        out_file.close();
      } else {
        LOG_FATAL << "Failed to open output file '" << options->write_file << "'.";
        return(false);
      }
    }

    return(true);
  } CASH_CATCH (...) {
    std::exception_ptr p = std::current_exception();
    std::string err("");
    err += (p ? p.__cxa_exception_type()->name() : "null");
    LOG_FATAL << "Error: "+err <<  std::endl;
    std::cerr << err << std::endl;
    return(false);
  }
}

std::unique_ptr<struct scanner_options> ParseScannerOptions(int argc, char** argv) {

  namespace po = boost::program_options;

  auto options = std::make_unique<scanner_options>();

  try {
    po::options_description desc("\n\
" + std::string(argv[0]) + " [OPTIONS] \n\
\n\
A block scanner.\n\
Use T1 to scan a tier 1 chain, T2 to scan a T2 chain, and scan to\n\
scan raw transactions.\n\
\n\
Required parameters");
    desc.add_options()
        ("mode", po::value<std::string>(), "Devcash mode (T1|T2|scan)")
        ("working-dir", po::value<std::string>(), "Directory where inputs are read and outputs are written")
        ("output", po::value<std::string>(), "Output file (JSON)")
        ;

    po::options_description d2("Optional parameters");
    d2.add_options()
        ("help", "produce help message")
        ("debug-mode", po::value<std::string>(), "Debug mode (on|off|perf) for testing")
        ("expect-version", "look for this block version while scanning")
        ("filter-by-address", po::value<std::string>(), "Filter results by address - only transactions involving this address will written to the JSON output file")
        ;
    desc.add(d2);

    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, desc), vm);
    po::notify(vm);

    if (vm.count("help")) {
      std::cout << desc << "\n";
      return nullptr;
    }

    if (vm.count("mode")) {
      std::string mode = vm["mode"].as<std::string>();
      if (mode == "SCAN") {
        options->mode = scan;
      } else if (mode == "T1") {
        options->mode = T1;
      } else if (mode == "T2") {
        options->mode = T2;
      } else {
        LOG_WARNING << "unknown mode: " << mode;
      }
      LOG_INFO << "mode: " << options->mode;
    } else {
      LOG_INFO << "mode was not set.";
    }

    if (vm.count("debug-mode")) {
      std::string debug_mode = vm["debug-mode"].as<std::string>();
      if (debug_mode == "on") {
        options->debug_mode = on;
      } else if (debug_mode == "toy") {
        options->debug_mode = toy;
      } else if (debug_mode == "perf") {
        options->debug_mode = perf;
      } else {
        options->debug_mode = off;
      }
      LOG_INFO << "debug_mode: " << options->debug_mode;
    } else {
      LOG_INFO << "debug_mode was not set.";
    }

    if (vm.count("working-dir")) {
      options->working_dir = vm["working-dir"].as<std::string>();
      LOG_INFO << "Working dir: " << options->working_dir;
    } else {
      LOG_INFO << "Working dir was not set.";
    }

    if (vm.count("filter-by-address")) {
      options->address_filter = vm["filter-by-address"].as<std::string>();
      LOG_INFO << "Address filter: " << options->address_filter;
    } else {
      LOG_INFO << "Address filter was not specified.";
    }

    if (vm.count("output")) {
      options->write_file = vm["output"].as<std::string>();
      LOG_INFO << "Output file: " << options->write_file;
    } else {
      LOG_INFO << "Output file was not set.";
    }

    if (vm.count("expect-version")) {
      options->version = vm["expect-version"].as<unsigned int>();
      LOG_INFO << "Expect Block Version: " << options->version;
    } else {
      LOG_INFO << "Expect Block Version was not set, defaulting to 0";
      options->version = 0;
    }
  }
  catch (std::exception& e) {
    LOG_ERROR << "error: " << e.what();
    return nullptr;
  }

  return options;
}

ext: cpp

max_stars_repo_name: spmckenney/Devv-Core

max_issues_repo_name: spmckenney/Devv-Core

max_forks_repo_name: spmckenney/Devv-Core


text:
//
// Created by nima on 21/07/17.
//

#include "TaskRingTransfer.h"
#include <custom_conversions/Conversions.h>
#include <boost/thread/thread.hpp>
#include <vtkAxesActor.h>

TaskRingTransfer::TaskRingTransfer()
{

    int n_views = 3;
    bool one_window_per_view = false;
    bool borders_off  = true;
    std::vector<int> view_resolution = {640, 480};
    std::vector<int> window_positions={1280, 0};
    // the only needed argument to construct a Renderer if the nodehandle ptr
    // The rest have default values.
    graphics = std::make_unique<Rendering>(view_resolution, false, n_views,
                                           one_window_per_view, borders_off,window_positions);

    // Define a master manipulator
    //    master = new Manipulator(nh, "/sigma7/sigma0", "/pose", "/gripper_angle");
    master = new Manipulator("/dvrk/PSM1_DUMMY",
                             "/position_cartesian_current",
                             "/gripper_position_current");
    // -------------------------------------------------------------------------
    // Create a cube for the board
    SimObject *board;
    {
        board_dimensions[0] = 0.14;
        board_dimensions[1] = 0.12;
        board_dimensions[2] = 0.01;

        double friction = 0.5;

        KDL::Frame pose;
        pose.p = KDL::Vector(
                - 0.01 + board_dimensions[0]/2,  board_dimensions[1]/2,
                -board_dimensions[2]/2);

        std::vector<double> dim = {
                board_dimensions[0], board_dimensions[1], board_dimensions[2]
        };
        board = new SimObject(ObjectShape::BOX, ObjectType::DYNAMIC, dim, pose,
                              0.0, friction);
        //    board->GetActor()->GetProperty()->SetOpacity(0.05);
        board->GetActor()->GetProperty()->SetColor(0.6, 0.5, 0.5);

        AddSimObjectToTask(board);
    }
    // -------------------------------------------------------------------------
    // static floor
    // always add a floor in under the workspace of your workd to prevent
    // objects falling too far and mess things up.
    std::vector<double> dims = {0., 0., 1., -0.5};
    SimObject* floor= new SimObject(ObjectShape::STATICPLANE, dims);
    dynamics_world->addRigidBody(floor->GetBody());

    // -------------------------------------------------------------------------
    // Create destination rods
    int cols = 4;
    int rows = 1;
    SimObject *cylinders[cols * rows];

    {
        float friction = 2.2;
        for (int i = 0; i < rows; ++i) {

            for (int j = 0; j < cols; ++j) {

                std::vector<double> dim = {0.002, 0.035};

                KDL::Frame pose(KDL::Rotation::Quaternion(0, 0.70711,
                                                          0.70711, 0.0),
                                KDL::Vector( 0.02 + (double) j * 0.028, 0.09,
                                             dim[1] / 2));

                cylinders[i * rows + j] =
                        new SimObject(ObjectShape::CYLINDER,
                                      ObjectType::DYNAMIC, dim, pose, 0.0,
                                      friction);

                auto ratio = (float) j / (float) cols;
                cylinders[i * rows + j]->GetActor()->GetProperty()->SetColor(
                        0.9 - 0.3 * ratio, 0.4, 0.4 + 0.4 * ratio);
                cylinders[i*rows+j]->GetActor()->GetProperty()
                        ->SetSpecular(0.8);
                cylinders[i*rows+j]->GetActor()->GetProperty()
                        ->SetSpecularPower(50);
                AddSimObjectToTask(cylinders[i * rows + j]);
            }
        }
    }

    // -------------------------------------------------------------------------
    // ROD
    SimObject* rod;
    {
        std::vector<double> rod_dim = {0.002, 0.1};
        KDL::Frame pose(KDL::Rotation::Quaternion(0.70711, 0.70711, 0.0, 0.0),
                        KDL::Vector(0.10, 0.03, 0.03));
        rod = new SimObject(ObjectShape::CYLINDER, ObjectType::DYNAMIC,
                                  rod_dim, pose);
        rod->GetActor()->GetProperty()->SetColor(0.3, 0.3, 0.3);
        AddSimObjectToTask(rod);

    }
    // -------------------------------------------------------------------------

    // -------------------------------------------------------------------------
    //// Create smallRING meshes
    size_t n_rings = 4;
    SimObject *rings[n_rings];
    {
        float density = 50000;
        float friction = 5;
        for (int l = 0; l < n_rings; ++l) {

            KDL::Frame pose(KDL::Rotation::Quaternion(0.70711, 0.70711, 0.0, 0.0),
                            KDL::Vector(0.06 + (double) l * 0.01, 0.03, 0.03));
            std::vector<double> dim; // not used

            rings[l] = new
                    SimObject(ObjectShape::MESH, ObjectType::DYNAMIC,
                              RESOURCES_DIRECTORY+"/mesh/task_Hook_ring_D2cm_D5mm.obj",
                              pose, density, friction);
            rings[l]->GetActor()->GetProperty()->SetColor(0., 0.5, 0.6);
            AddSimObjectToTask(rings[l]);

        }
    }
// -------------------------------------------------------------------------
    // Create kinematic gripper
    //
    // The gripper is a 5 link mechanism, link 0 is a base link, 1st and 2nd
    // are angular jaws (like a scissor) so their orientation is related to
    // the gripper angle of the master, 3rd and 4th change position along one
    // axis according to the gripper angle, like a clamp. These last links
    // are used to generate enough normal force for a stable grasping, since
    // the scissor type links push the objects outwards.
    // make sure tu set a high friction coefficient for the objects you want
    // to grasp.
    // In addition, make sure you limit the gripper angle so that the user
    // can't press the object too much. Otherwise the injected energy would
    // be so high that no friction can compensate it.
    {
        gripper_link_dims =
                {{0.003, 0.003, 0.005}
                        , {0.004, 0.001, 0.009}
                        , {0.004, 0.001, 0.009}
                        , {0.004, 0.001, 0.007}
                        , {0.004, 0.001, 0.007}};

        grippers[0] = new SimFiveLinkGripper(gripper_link_dims);
        AddSimMechanismToTask(grippers[0]);
        std::vector<std::vector<double>> gripper_3link_dims =
                {{0.002, 0.002, 0.005}
                        , {0.004, 0.001, 0.009}
                        , {0.004, 0.001, 0.009}};

    }

    // -------------------------------------------------------------------------
    //// Create hook mesh
    {
        KDL::Frame pose(KDL::Rotation::Quaternion(0.7, 0, 0.7, 0),
                        KDL::Vector(0.09, 0.07, 0.08));
        std::vector<double> dim; // not used
        float density = 50000;

        hook_mesh = new
                SimObject(ObjectShape::MESH, ObjectType::KINEMATIC,
                          RESOURCES_DIRECTORY+"/mesh/task_hook_hook.obj", pose,
                          density, 0);
        hook_mesh->GetActor()->GetProperty()->SetColor(1., 1.0, 1.0);
        AddSimObjectToTask(hook_mesh);
    }

    // -------------------------------------------------------------------------
    // FRAMES
    vtkSmartPointer<vtkAxesActor> task_coordinate_axes =
            vtkSmartPointer<vtkAxesActor>::New();

    task_coordinate_axes->SetXAxisLabelText("");
    task_coordinate_axes->SetYAxisLabelText("");
    task_coordinate_axes->SetZAxisLabelText("");
    task_coordinate_axes->SetTotalLength(0.01, 0.01, 0.01);
    task_coordinate_axes->SetShaftType(vtkAxesActor::CYLINDER_SHAFT);
    bool show_ref_frames = 1;
    if(show_ref_frames)
        graphics->AddActorToScene(task_coordinate_axes, false);

};

//------------------------------------------------------------------------------
void TaskRingTransfer::TaskLoop() {

    //-------------------------------- UPDATE RIGHT GRIPPER
    KDL::Frame grpr_right_pose;
    master->GetPoseWorld(grpr_right_pose);
    // map gripper value to an angle

    double grip_posit;
    master->GetGripper(grip_posit);
    double theta_min=0*M_PI/180;
    double theta_max=40*M_PI/180;
    double grip_angle = theta_max*(grip_posit+0.5)/1.55;
    if(grip_angle<theta_min)
        grip_angle=theta_min;

    grippers[0]->SetPoseAndJawAngle(grpr_right_pose, grip_angle);

    //Update the pose of hook
    {
        KDL::Frame tool_pose;
        KDL::Frame local_tool_transform;

        // locally transform the tool if needed
        local_tool_transform.p = KDL::Vector(0.0, 0.0, 0);
        local_tool_transform.M.DoRotY(0);
        tool_pose = tool_pose * local_tool_transform;

        //KDL::Vector box_posit = tool_pose * KDL::Vector(0.0, 0.0, 0);
        hook_mesh->SetKinematicPose(tool_pose);

    }

}

custom_msgs::TaskState TaskRingTransfer::GetTaskStateMsg() {
    custom_msgs::TaskState task_state_msg;
    return task_state_msg;
}

void TaskRingTransfer::ResetTask() {
    ROS_INFO("Resetting the task.");

}

void TaskRingTransfer::ResetCurrentAcquisition() {
    ROS_INFO("Resetting current acquisition.");

}

void TaskRingTransfer::HapticsThread() {

    ros::Publisher pub_desired[2];

    ros::NodeHandlePtr node = boost::make_shared<ros::NodeHandle>();
    pub_desired[0] = node->advertise<geometry_msgs::PoseStamped>
            ("/PSM1/tool_pose_desired", 10);
//    if(bimanual)
//        pub_desired[1] = node->advertise<geometry_msgs::PoseStamped>
//                ("/PSM2/tool_pose_desired", 10);

    ros::Rate loop_rate(200);

    while (ros::ok())
    {

//        CalculatedDesiredToolPose();

//        // publish desired poses
//        for (int n_arm = 0; n_arm < 1+ int(bimanual); ++n_arm) {
//
//            // convert to pose message
//            geometry_msgs::PoseStamped pose_msg;
//            tf::poseKDLToMsg(tool_desired_pose_kdl[n_arm], pose_msg.pose);
//            // fill the header
//            pose_msg.header.frame_id = "/task_space";
//            pose_msg.header.stamp = ros::Time::now();
//            // publish
//            pub_desired[n_arm].publish(pose_msg);
//        }

        ros::spinOnce();
        loop_rate.sleep();
        boost::this_thread::interruption_point();
    }
}

TaskRingTransfer::~TaskRingTransfer() {

    delete master;

}

max_stars_repo_name: neemoh/ART

max_issues_repo_name: liuxia-zju/ATAR

max_forks_repo_name: liuxia-zju/ATAR


text:
/*
 * Copyright 2015 Cloudius Systems
 */

#include <seastar/http/httpd.hh>
#include <seastar/http/handlers.hh>
#include <seastar/http/matcher.hh>
#include <seastar/http/matchrules.hh>
#include <seastar/json/formatter.hh>
#include <seastar/http/routes.hh>
#include <seastar/http/exception.hh>
#include <seastar/http/transformers.hh>
#include <seastar/core/do_with.hh>
#include <seastar/core/loop.hh>
#include <seastar/core/when_all.hh>
#include <seastar/testing/test_case.hh>
#include <seastar/testing/thread_test_case.hh>
#include "loopback_socket.hh"
#include <boost/algorithm/string.hpp>
#include <seastar/core/thread.hh>
#include <seastar/util/noncopyable_function.hh>
#include <seastar/http/json_path.hh>
#include <seastar/http/response_parser.hh>
#include <sstream>
#include <seastar/core/shared_future.hh>
#include <seastar/util/later.hh>

using namespace seastar;
using namespace httpd;

class handl : public httpd::handler_base {
public:
    virtual future<std::unique_ptr<reply> > handle(const sstring& path,
            std::unique_ptr<request> req, std::unique_ptr<reply> rep) {
        rep->done("html");
        return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
    }
};

SEASTAR_TEST_CASE(test_reply)
{
    reply r;
    r.set_content_type("txt");
    BOOST_REQUIRE_EQUAL(r._headers["Content-Type"], sstring("text/plain"));
    return make_ready_future<>();
}

SEASTAR_TEST_CASE(test_str_matcher)
{

    str_matcher m("/hello");
    parameters param;
    BOOST_REQUIRE_EQUAL(m.match("/abc/hello", 4, param), 10u);
    return make_ready_future<>();
}

SEASTAR_TEST_CASE(test_param_matcher)
{

    param_matcher m("param");
    parameters param;
    BOOST_REQUIRE_EQUAL(m.match("/abc/hello", 4, param), 10u);
    BOOST_REQUIRE_EQUAL(param.path("param"), "/hello");
    BOOST_REQUIRE_EQUAL(param["param"], "hello");
    return make_ready_future<>();
}

SEASTAR_TEST_CASE(test_match_rule)
{

    parameters param;
    handl* h = new handl();
    match_rule mr(h);
    mr.add_str("/hello").add_param("param");
    httpd::handler_base* res = mr.get("/hello/val1", param);
    BOOST_REQUIRE_EQUAL(res, h);
    BOOST_REQUIRE_EQUAL(param["param"], "val1");
    res = mr.get("/hell/val1", param);
    httpd::handler_base* nl = nullptr;
    BOOST_REQUIRE_EQUAL(res, nl);
    return make_ready_future<>();
}

SEASTAR_TEST_CASE(test_match_rule_order)
{
    parameters param;
    routes route;

    handl* h1 = new handl();
    route.add(operation_type::GET, url("/hello"), h1);

    handl* h2 = new handl();
    route.add(operation_type::GET, url("/hello"), h2);

    auto rh = route.get_handler(GET, "/hello", param);
    BOOST_REQUIRE_EQUAL(rh, h1);

    return make_ready_future<>();
}

SEASTAR_TEST_CASE(test_put_drop_rule)
{
    routes rts;
    auto h = std::make_unique<handl>();
    parameters params;

    {
        auto reg = handler_registration(rts, *h, "/hello", operation_type::GET);
        auto res = rts.get_handler(operation_type::GET, "/hello", params);
        BOOST_REQUIRE_EQUAL(res, h.get());
    }

    auto res = rts.get_handler(operation_type::GET, "/hello", params);
    httpd::handler_base* nl = nullptr;
    BOOST_REQUIRE_EQUAL(res, nl);
    return make_ready_future<>();
}

// Putting a duplicated exact rule would result
// in a memory leak due to the fact that rules are implemented
// as raw pointers. In order to prevent such leaks,
// an exception is thrown if somebody tries to put
// a duplicated rule without removing the old one first.
// The interface demands that the callee allocates the handle,
// so it should also expect the callee to free it before
// overwriting.
SEASTAR_TEST_CASE(test_duplicated_exact_rule)
{
    parameters param;
    routes route;

    handl* h1 = new handl;
    route.put(operation_type::GET, "/hello", h1);

    handl* h2 = new handl;
    BOOST_REQUIRE_THROW(route.put(operation_type::GET, "/hello", h2), std::runtime_error);

    delete route.drop(operation_type::GET, "/hello");
    route.put(operation_type::GET, "/hello", h2);

    return make_ready_future<>();
}

SEASTAR_TEST_CASE(test_add_del_cookie)
{
    routes rts;
    handl* h = new handl();
    match_rule mr(h);
    mr.add_str("/hello");
    parameters params;

    {
        auto reg = rule_registration(rts, mr, operation_type::GET);
        auto res = rts.get_handler(operation_type::GET, "/hello", params);
        BOOST_REQUIRE_EQUAL(res, h);
    }

    auto res = rts.get_handler(operation_type::GET, "/hello", params);
    httpd::handler_base* nl = nullptr;
    BOOST_REQUIRE_EQUAL(res, nl);
    return make_ready_future<>();
}

SEASTAR_TEST_CASE(test_formatter)
{
    BOOST_REQUIRE_EQUAL(json::formatter::to_json(true), "true");
    BOOST_REQUIRE_EQUAL(json::formatter::to_json(false), "false");
    BOOST_REQUIRE_EQUAL(json::formatter::to_json(1), "1");
    const char* txt = "efg";
    BOOST_REQUIRE_EQUAL(json::formatter::to_json(txt), "\"efg\"");
    sstring str = "abc";
    BOOST_REQUIRE_EQUAL(json::formatter::to_json(str), "\"abc\"");
    float f = 1;
    BOOST_REQUIRE_EQUAL(json::formatter::to_json(f), "1");
    f = 1.0/0.0;
    BOOST_CHECK_THROW(json::formatter::to_json(f), std::out_of_range);
    f = -1.0/0.0;
    BOOST_CHECK_THROW(json::formatter::to_json(f), std::out_of_range);
    f = 0.0/0.0;
    BOOST_CHECK_THROW(json::formatter::to_json(f), std::invalid_argument);
    double d = -1;
    BOOST_REQUIRE_EQUAL(json::formatter::to_json(d), "-1");
    d = 1.0/0.0;
    BOOST_CHECK_THROW(json::formatter::to_json(d), std::out_of_range);
    d = -1.0/0.0;
    BOOST_CHECK_THROW(json::formatter::to_json(d), std::out_of_range);
    d = 0.0/0.0;
    BOOST_CHECK_THROW(json::formatter::to_json(d), std::invalid_argument);
    return make_ready_future<>();
}

SEASTAR_TEST_CASE(test_decode_url) {
    request req;
    req._url = "/a?q=%23%24%23";
    sstring url = http_server::connection::set_query_param(req);
    BOOST_REQUIRE_EQUAL(url, "/a");
    BOOST_REQUIRE_EQUAL(req.get_query_param("q"), "#$#");
    req._url = "/a?a=%23%24%23&b=%22%26%22";
    http_server::connection::set_query_param(req);
    BOOST_REQUIRE_EQUAL(req.get_query_param("a"), "#$#");
    BOOST_REQUIRE_EQUAL(req.get_query_param("b"), "\"&\"");
    return make_ready_future<>();
}

SEASTAR_TEST_CASE(test_routes) {
    handl* h1 = new handl();
    handl* h2 = new handl();
    routes route;
    route.add(operation_type::GET, url("/api").remainder("path"), h1);
    route.add(operation_type::GET, url("/"), h2);
    std::unique_ptr<request> req = std::make_unique<request>();
    std::unique_ptr<reply> rep = std::make_unique<reply>();

    auto f1 =
            route.handle("/api", std::move(req), std::move(rep)).then(
                    [] (std::unique_ptr<reply> rep) {
                        BOOST_REQUIRE_EQUAL((int )rep->_status, (int )reply::status_type::ok);
                    });
    req.reset(new request);
    rep.reset(new reply);

    auto f2 =
            route.handle("/", std::move(req), std::move(rep)).then(
                    [] (std::unique_ptr<reply> rep) {
                        BOOST_REQUIRE_EQUAL((int )rep->_status, (int )reply::status_type::ok);
                    });
    req.reset(new request);
    rep.reset(new reply);
    auto f3 =
            route.handle("/api/abc", std::move(req), std::move(rep)).then(
                    [] (std::unique_ptr<reply> rep) {
                    });
    req.reset(new request);
    rep.reset(new reply);
    auto f4 =
            route.handle("/ap", std::move(req), std::move(rep)).then(
                    [] (std::unique_ptr<reply> rep) {
                        BOOST_REQUIRE_EQUAL((int )rep->_status,
                                            (int )reply::status_type::not_found);
                    });
    return when_all(std::move(f1), std::move(f2), std::move(f3), std::move(f4))
            .then([] (std::tuple<future<>, future<>, future<>, future<>> fs) {
        std::get<0>(fs).get();
        std::get<1>(fs).get();
        std::get<2>(fs).get();
        std::get<3>(fs).get();
    });
}

SEASTAR_TEST_CASE(test_json_path) {
    shared_ptr<bool> res1 = make_shared<bool>(false);
    shared_ptr<bool> res2 = make_shared<bool>(false);
    shared_ptr<bool> res3 = make_shared<bool>(false);
    shared_ptr<routes> route = make_shared<routes>();
    path_description path1("/my/path",GET,"path1",
        {{"param1", path_description::url_component_type::PARAM}
        ,{"/text", path_description::url_component_type::FIXED_STRING}},{});
    path_description path2("/my/path",GET,"path2",
            {{"param1", path_description::url_component_type::PARAM}
            ,{"param2", path_description::url_component_type::PARAM}},{});
    path_description path3("/my/path",GET,"path3",
            {{"param1", path_description::url_component_type::PARAM}
            ,{"param2", path_description::url_component_type::PARAM_UNTIL_END_OF_PATH}},{});

    path1.set(*route, [res1] (const_req req) {
        (*res1) = true;
        BOOST_REQUIRE_EQUAL(req.param["param1"], "value1");
        return "";
    });

    path2.set(*route, [res2] (const_req req) {
        (*res2) = true;
        BOOST_REQUIRE_EQUAL(req.param["param1"], "value2");
        BOOST_REQUIRE_EQUAL(req.param["param2"], "text1");
        return "";
    });

    path3.set(*route, [res3] (const_req req) {
        (*res3) = true;
        BOOST_REQUIRE_EQUAL(req.param["param1"], "value3");
        BOOST_REQUIRE_EQUAL(req.param["param2"], "text2/text3");
        return "";
    });

    auto f1 = route->handle("/my/path/value1/text", std::make_unique<request>(), std::make_unique<reply>()).then([res1, route] (auto f) {
        BOOST_REQUIRE_EQUAL(*res1, true);
    });

    auto f2 = route->handle("/my/path/value2/text1", std::make_unique<request>(), std::make_unique<reply>()).then([res2, route] (auto f) {
        BOOST_REQUIRE_EQUAL(*res2, true);
    });

    auto f3 = route->handle("/my/path/value3/text2/text3", std::make_unique<request>(), std::make_unique<reply>()).then([res3, route] (auto f) {
        BOOST_REQUIRE_EQUAL(*res3, true);
    });

    return when_all(std::move(f1), std::move(f2), std::move(f3))
                .then([] (std::tuple<future<>, future<>, future<>> fs) {
            std::get<0>(fs).get();
            std::get<1>(fs).get();
            std::get<2>(fs).get();
    });
}

/*!
 * \brief a helper data sink that stores everything it gets in a stringstream
 */
class memory_data_sink_impl : public data_sink_impl {
    std::stringstream& _ss;
public:
    memory_data_sink_impl(std::stringstream& ss) : _ss(ss) {
    }
    virtual future<> put(net::packet data)  override {
        abort();
        return make_ready_future<>();
    }
    virtual future<> put(temporary_buffer<char> buf) override {
        _ss.write(buf.get(), buf.size());
        return make_ready_future<>();
    }
    virtual future<> flush() override {
        return make_ready_future<>();
    }

    virtual future<> close() override {
        return make_ready_future<>();
    }
};

class memory_data_sink : public data_sink {
public:
    memory_data_sink(std::stringstream& ss)
        : data_sink(std::make_unique<memory_data_sink_impl>(ss)) {}
};

future<> test_transformer_stream(std::stringstream& ss, content_replace& cr, std::vector<sstring>&& buffer_parts) {
    std::unique_ptr<seastar::httpd::request> req = std::make_unique<seastar::httpd::request>();
    ss.str("");
    req->_headers["Host"] = "localhost";
    output_stream_options opts;
    opts.trim_to_size = true;
    return do_with(output_stream<char>(cr.transform(std::move(req), "json", output_stream<char>(memory_data_sink(ss), 32000, opts))),
            std::vector<sstring>(std::move(buffer_parts)), [] (output_stream<char>& os, std::vector<sstring>& parts) {
        return do_for_each(parts, [&os](auto& p) {
            return os.write(p);
        }).then([&os] {
            return os.close();
        });
    });
}

SEASTAR_TEST_CASE(test_transformer) {
    return do_with(std::stringstream(), content_replace("json"), [] (std::stringstream& ss, content_replace& cr) {
        output_stream_options opts;
        opts.trim_to_size = true;
        return do_with(output_stream<char>(cr.transform(std::make_unique<seastar::httpd::request>(), "html", output_stream<char>(memory_data_sink(ss), 32000, opts))),
                [] (output_stream<char>& os) {
            return os.write(sstring("hello-{{Protocol}}-xyz-{{Host}}")).then([&os] {
                return os.close();
            });
        }).then([&ss, &cr] () {
            BOOST_REQUIRE_EQUAL(ss.str(), "hello-{{Protocol}}-xyz-{{Host}}");
            return test_transformer_stream(ss, cr, {"hell", "o-{", "{Pro", "tocol}}-xyz-{{Ho", "st}}{{Pr"}).then([&ss, &cr] {
                BOOST_REQUIRE_EQUAL(ss.str(), "hello-http-xyz-localhost{{Pr");
                return test_transformer_stream(ss, cr, {"hell", "o-{{", "Pro", "tocol}}{{Protocol}}-{{Protoxyz-{{Ho", "st}}{{Pr"}).then([&ss, &cr] {
                    BOOST_REQUIRE_EQUAL(ss.str(), "hello-httphttp-{{Protoxyz-localhost{{Pr");
                    return test_transformer_stream(ss, cr, {"hell", "o-{{Pro", "t{{Protocol}}ocol}}", "{{Host}}"}).then([&ss] {
                        BOOST_REQUIRE_EQUAL(ss.str(), "hello-{{Prothttpocol}}localhost");
                    });
                });
            });
        });
    });
}

struct http_consumer {
    std::map<sstring, std::string> _headers;
    std::string _body;
    uint32_t _remain = 0;
    std::string _current;
    char last = '\0';
    uint32_t _size = 0;
    bool _concat = true;

    enum class status_type {
        READING_HEADERS,
        CHUNK_SIZE,
        CHUNK_BODY,
        CHUNK_END,
        READING_BODY_BY_SIZE,
        DONE
    };
    status_type status = status_type::READING_HEADERS;

    bool read(const temporary_buffer<char>& b) {
        for (auto c : b) {
            if (last =='\r' && c == '\n') {
                if (_current == "") {
                    if (status == status_type::READING_HEADERS || (status == status_type::CHUNK_BODY && _remain == 0)) {
                        if (status == status_type::READING_HEADERS && _headers.find("Content-Length") != _headers.end()) {
                            _remain = stoi(_headers["Content-Length"], nullptr, 16);
                            if (_remain == 0) {
                                status = status_type::DONE;
                                break;
                            }
                            status = status_type::READING_BODY_BY_SIZE;
                        } else {
                            status = status_type::CHUNK_SIZE;
                        }
                    } else if (status == status_type::CHUNK_END) {
                        status = status_type::DONE;
                        break;
                    }
                } else {
                    switch (status) {
                    case status_type::READING_HEADERS: add_header(_current);
                    break;
                    case status_type::CHUNK_SIZE: set_chunk(_current);
                    break;
                    default:
                        break;
                    }
                    _current = "";
                }
                last = '\0';
            } else {
                if (last != '\0') {
                    if (status == status_type::CHUNK_BODY || status == status_type::READING_BODY_BY_SIZE) {
                        if (_concat) {
                            _body = _body + last;
                        }
                        _size++;
                        _remain--;
                        if (_remain <= 1 && status == status_type::READING_BODY_BY_SIZE) {
                            if (_concat) {
                                _body = _body + c;
                            }
                            _size++;
                            status = status_type::DONE;
                            break;
                        }
                    } else {
                        _current = _current + last;
                    }

                }
                last = c;
            }
        }
        return status == status_type::DONE;
    }

    void set_chunk(const std::string& s) {
        _remain = stoi(s, nullptr, 16);
        if (_remain == 0) {
            status = status_type::CHUNK_END;
        } else {
            status = status_type::CHUNK_BODY;
        }
    }

    void add_header(const std::string& s) {
        std::vector<std::string> strs;
        boost::split(strs, s, boost::is_any_of(":"));
        if (strs.size() > 1) {
            _headers[strs[0]] = strs[1];
        }
    }
};

class test_client_server {
public:
    static future<> write_request(output_stream<char>& output) {
        return output.write(sstring("GET /test HTTP/1.1\r\nHost: myhost.org\r\n\r\n")).then([&output]{
                return output.flush();
        });
    }

    static future<> run_test(std::function<future<>(output_stream<char> &&)>&& write_func, std::function<bool(size_t, http_consumer&)> reader) {
        return do_with(loopback_connection_factory(), foreign_ptr<shared_ptr<http_server>>(make_shared<http_server>("test")),
                [reader, &write_func] (loopback_connection_factory& lcf, auto& server) {
            return do_with(loopback_socket_impl(lcf), [&server, &lcf, reader, &write_func](loopback_socket_impl& lsi) {
                httpd::http_server_tester::listeners(*server).emplace_back(lcf.get_server_socket());

                auto client = seastar::async([&lsi, reader] {
                    connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
                    input_stream<char> input(c_socket.input());
                    output_stream<char> output(c_socket.output());
                    bool more = true;
                    size_t count = 0;
                    while (more) {
                        http_consumer htp;
                        htp._concat = false;

                        write_request(output).get();
                        repeat([&input, &htp] {
                            return input.read().then([&htp](const temporary_buffer<char>& b) mutable {
                                return (b.size() == 0 || htp.read(b)) ? make_ready_future<stop_iteration>(stop_iteration::yes) :
                                        make_ready_future<stop_iteration>(stop_iteration::no);
                            });
                        }).get();
                        std::cout << htp._body << std::endl;
                        more = reader(count, htp);
                        count++;
                    }
                    if (input.eof()) {
                        input.close().get();
                    }
                });

                auto server_setup = seastar::async([&server, &write_func] {
                    class test_handler : public handler_base {
                        size_t count = 0;
                        http_server& _server;
                        std::function<future<>(output_stream<char> &&)> _write_func;
                        promise<> _all_message_sent;
                    public:
                        test_handler(http_server& server, std::function<future<>(output_stream<char> &&)>&& write_func) : _server(server), _write_func(write_func) {
                        }
                        future<std::unique_ptr<reply>> handle(const sstring& path,
                                std::unique_ptr<request> req, std::unique_ptr<reply> rep) override {
                            rep->write_body("json", std::move(_write_func));
                            count++;
                            _all_message_sent.set_value();
                            return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
                        }
                        future<> wait_for_message() {
                            return _all_message_sent.get_future();
                        }
                    };
                    auto handler = new test_handler(*server, std::move(write_func));
                    server->_routes.put(GET, "/test", handler);
                    when_all(server->do_accepts(0), handler->wait_for_message()).get();
                });
                return when_all(std::move(client), std::move(server_setup));
            }).discard_result().then_wrapped([&server] (auto f) {
                f.ignore_ready_future();
                return server->stop();
            });
        });
    }
    static future<> run(std::vector<std::tuple<bool, size_t>> tests) {
        return do_with(loopback_connection_factory(), foreign_ptr<shared_ptr<http_server>>(make_shared<http_server>("test")),
                [tests] (loopback_connection_factory& lcf, auto& server) {
            return do_with(loopback_socket_impl(lcf), [&server, &lcf, tests](loopback_socket_impl& lsi) {
                httpd::http_server_tester::listeners(*server).emplace_back(lcf.get_server_socket());

                auto client = seastar::async([&lsi, tests] {
                    connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
                    input_stream<char> input(c_socket.input());
                    output_stream<char> output(c_socket.output());
                    bool more = true;
                    size_t count = 0;
                    while (more) {
                        http_consumer htp;
                        write_request(output).get();
                        repeat([&input, &htp] {
                            return input.read().then([&htp](const temporary_buffer<char>& b) mutable {
                                return (b.size() == 0 || htp.read(b)) ? make_ready_future<stop_iteration>(stop_iteration::yes) :
                                        make_ready_future<stop_iteration>(stop_iteration::no);
                            });
                        }).get();
                        if (std::get<bool>(tests[count])) {
                            BOOST_REQUIRE_EQUAL(htp._body.length(), std::get<size_t>(tests[count]));
                        } else {
                            BOOST_REQUIRE_EQUAL(input.eof(), true);
                            more = false;
                        }
                        count++;
                        if (count == tests.size()) {
                            more = false;
                        }
                    }
                    if (input.eof()) {
                        input.close().get();
                    }
                });

                auto server_setup = seastar::async([&server, tests] {
                    class test_handler : public handler_base {
                        size_t count = 0;
                        http_server& _server;
                        std::vector<std::tuple<bool, size_t>> _tests;
                        promise<> _all_message_sent;
                    public:
                        test_handler(http_server& server, const std::vector<std::tuple<bool, size_t>>& tests) : _server(server), _tests(tests) {
                        }
                        future<std::unique_ptr<reply>> handle(const sstring& path,
                                std::unique_ptr<request> req, std::unique_ptr<reply> rep) override {
                            rep->write_body("txt", make_writer(std::get<size_t>(_tests[count]), std::get<bool>(_tests[count])));
                            count++;
                            if (count == _tests.size()) {
                                _all_message_sent.set_value();
                            }
                            return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
                        }
                        future<> wait_for_message() {
                            return _all_message_sent.get_future();
                        }
                    };
                    auto handler = new test_handler(*server, tests);
                    server->_routes.put(GET, "/test", handler);
                    when_all(server->do_accepts(0), handler->wait_for_message()).get();
                });
                return when_all(std::move(client), std::move(server_setup));
            }).discard_result().then_wrapped([&server] (auto f) {
                f.ignore_ready_future();
                return server->stop();
            });
        });
    }

    static noncopyable_function<future<>(output_stream<char>&& o_stream)> make_writer(size_t len, bool success) {
        return [len, success] (output_stream<char>&& o_stream) mutable {
            return do_with(output_stream<char>(std::move(o_stream)), uint32_t(len/10), [success](output_stream<char>& str, uint32_t& remain) {
                if (remain == 0) {
                    if (success) {
                        return str.close();
                    } else {
                        throw std::runtime_error("Throwing exception before writing");
                    }
                }
                return repeat([&str, &remain] () mutable {
                    return str.write("1234567890").then([&remain]() mutable {
                        remain--;
                        return (remain == 0)? make_ready_future<stop_iteration>(stop_iteration::yes) : make_ready_future<stop_iteration>(stop_iteration::no);
                    });
                }).then([&str, success] {
                    if (!success) {
                        return str.flush();
                    }
                    return make_ready_future<>();
                }).then([&str, success] {
                    if (success) {
                        return str.close();
                    } else {
                        throw std::runtime_error("Throwing exception after writing");
                    }
                });
            });
        };
    }
};

SEASTAR_TEST_CASE(test_message_with_error_non_empty_body) {
    std::vector<std::tuple<bool, size_t>> tests = {
        std::make_tuple(true, 100),
        std::make_tuple(false, 10000)};
    return test_client_server::run(tests);
}

SEASTAR_TEST_CASE(test_simple_chunked) {
    std::vector<std::tuple<bool, size_t>> tests = {
        std::make_tuple(true, 100000),
        std::make_tuple(true, 100)};
    return test_client_server::run(tests);
}

SEASTAR_TEST_CASE(test_http_client_server_full) {
    std::vector<std::tuple<bool, size_t>> tests = {
        std::make_tuple(true, 100),
        std::make_tuple(true, 10000),
        std::make_tuple(true, 100),
        std::make_tuple(true, 0),
        std::make_tuple(true, 5000),
        std::make_tuple(true, 10000),
        std::make_tuple(true, 9000),
        std::make_tuple(true, 10000)};
    return test_client_server::run(tests);
}

/*
 * return string in the given size
 * The string size takes the quotes into consideration.
 */
std::string get_value(int size) {
    std::stringstream res;
    for (auto i = 0; i < size - 2; i++) {
        res << "a";
    }
    return res.str();
}

/*
 * A helper object that map to a big json string
 * in the format of:
 * {"valu": "aaa....aa", "valu": "aaa....aa", "valu": "aaa....aa"...}
 *
 * The object can have an arbitrary size in multiplication of 10000 bytes
 *  */
struct extra_big_object : public json::json_base {
    json::json_element<sstring>* value;
    extra_big_object(size_t size) {
        value = new json::json_element<sstring>;
        // size = brackets + (name + ": " + get_value) * n + ", " * (n-1)
        // size = 2 + (name + 6 + get_value) * n - 2
        value->_name = "valu";
        *value = get_value(9990);
        for (size_t i = 0; i < size/10000; i++) {
            _elements.emplace_back(value);
        }
    }

    virtual ~extra_big_object() {
        delete value;
    }

    extra_big_object(const extra_big_object& o) {
        value = new json::json_element<sstring>;
        value->_name = o.value->_name;
        *value = (*o.value)();
        for (size_t i = 0; i < o._elements.size(); i++) {
            _elements.emplace_back(value);
        }
    }
};

SEASTAR_TEST_CASE(json_stream) {
    std::vector<extra_big_object> vec;
    size_t num_objects = 1000;
    size_t total_size = num_objects * 1000001 + 1;
    for (size_t i = 0; i < num_objects; i++) {
        vec.emplace_back(1000000);
    }
    return test_client_server::run_test(json::stream_object(vec), [total_size](size_t s, http_consumer& h) {
        BOOST_REQUIRE_EQUAL(h._size, total_size);
        return false;
    });
}

class json_test_handler : public handler_base {
    std::function<future<>(output_stream<char> &&)> _write_func;
public:
    json_test_handler(std::function<future<>(output_stream<char> &&)>&& write_func) : _write_func(write_func) {
    }
    future<std::unique_ptr<reply>> handle(const sstring& path,
            std::unique_ptr<request> req, std::unique_ptr<reply> rep) override {
        rep->write_body("json", _write_func);
        return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
    }
};

SEASTAR_TEST_CASE(content_length_limit) {
    return seastar::async([] {
        loopback_connection_factory lcf;
        http_server server("test");
        server.set_content_length_limit(11);
        loopback_socket_impl lsi(lcf);
        httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());

        future<> client = seastar::async([&lsi] {
            connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
            input_stream<char> input(c_socket.input());
            output_stream<char> output(c_socket.output());

            output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\n\r\n")).get();
            output.flush().get();
            auto resp = input.read().get0();
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);

            output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 11\r\n\r\nxxxxxxxxxxx")).get();
            output.flush().get();
            resp = input.read().get0();
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);

            output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 17\r\n\r\nxxxxxxxxxxxxxxxx")).get();
            output.flush().get();
            resp = input.read().get0();
            BOOST_REQUIRE_EQUAL(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("413 Payload Too Large"), std::string::npos);

            input.close().get();
            output.close().get();
        });

        auto handler = new json_test_handler(json::stream_object("hello"));
        server._routes.put(GET, "/test", handler);
        server.do_accepts(0).get();

        client.get();
        server.stop().get();
    });
}

SEASTAR_TEST_CASE(test_100_continue) {
    return seastar::async([] {
        loopback_connection_factory lcf;
        http_server server("test");
        server.set_content_length_limit(11);
        loopback_socket_impl lsi(lcf);
        httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
        future<> client = seastar::async([&lsi] {
            connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
            input_stream<char> input(c_socket.input());
            output_stream<char> output(c_socket.output());

            for (auto version : {sstring("1.0"), sstring("1.1")}) {
                for (auto content : {sstring(""), sstring("xxxxxxxxxxx")}) {
                    for (auto expect : {sstring(""), sstring("Expect: 100-continue\r\n"), sstring("Expect: 100-cOnTInUE\r\n")}) {
                        auto content_len = content.empty() ? sstring("") : (sstring("Content-Length: ") + to_sstring(content.length()) + sstring("\r\n"));
                        sstring req = sstring("GET /test HTTP/") + version + sstring("\r\nHost: test\r\nConnection: Keep-Alive\r\n") + content_len + expect + sstring("\r\n");
                        output.write(req).get();
                        output.flush().get();
                        bool already_ok = false;
                        if (version == "1.1" && expect.length()) {
                            auto resp = input.read().get0();
                            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("100 Continue"), std::string::npos);
                            already_ok = content.empty() && std::string(resp.get(), resp.size()).find("200 OK") != std::string::npos;
                        }
                        if (!already_ok) {
                            //If the body is empty, the final response might have already been read
                            output.write(content).get();
                            output.flush().get();
                            auto resp = input.read().get0();
                            BOOST_REQUIRE_EQUAL(std::string(resp.get(), resp.size()).find("100 Continue"), std::string::npos);
                            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
                        }
                    }
                }
            }
            output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 17\r\nExpect: 100-continue\r\n\r\n")).get();
            output.flush().get();
            auto resp = input.read().get0();
            BOOST_REQUIRE_EQUAL(std::string(resp.get(), resp.size()).find("100 Continue"), std::string::npos);
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("413 Payload Too Large"), std::string::npos);

            input.close().get();
            output.close().get();
        });

        auto handler = new json_test_handler(json::stream_object("hello"));
        server._routes.put(GET, "/test", handler);
        server.do_accepts(0).get();

        client.get();
        server.stop().get();
    });
}

SEASTAR_TEST_CASE(test_unparsable_request) {
    // Test if a message that cannot be parsed as a http request is being replied with a 400 Bad Request response
    return seastar::async([] {
        loopback_connection_factory lcf;
        http_server server("test");
        loopback_socket_impl lsi(lcf);
        httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
        future<> client = seastar::async([&lsi] {
            connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
            input_stream<char> input(c_socket.input());
            output_stream<char> output(c_socket.output());

            output.write(sstring("GET /test HTTP/1.1\r\nhello\r\nContent-Length: 17\r\nExpect: 100-continue\r\n\r\n")).get();
            output.flush().get();
            auto resp = input.read().get0();
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("400 Bad Request"), std::string::npos);
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("Can't parse the request"), std::string::npos);

            input.close().get();
            output.close().get();
        });

        auto handler = new json_test_handler(json::stream_object("hello"));
        server._routes.put(GET, "/test", handler);
        server.do_accepts(0).get();

        client.get();
        server.stop().get();
    });
}

/*
 * A request handler that responds with the same body that was used in the request using the requests content_stream
 *  */
struct echo_stream_handler : public handler_base {
    echo_stream_handler() = default;
    future<std::unique_ptr<reply>> handle(const sstring& path,
            std::unique_ptr<request> req, std::unique_ptr<reply> rep) override {
        return do_with(std::move(req), std::move(rep), sstring(), [] (std::unique_ptr<request>& req, std::unique_ptr<reply>& rep, sstring& rep_content) {
            return do_until([&req] { return req->content_stream->eof(); }, [&req, &rep_content] {
                return req->content_stream->read().then([&rep_content] (temporary_buffer<char> tmp) {
                    rep_content += to_sstring(std::move(tmp));
                });
            }).then([&req, &rep, &rep_content] {
                for (auto it : req->chunk_extensions) {
                    rep_content += it.first;
                    if (it.second != "") {
                        rep_content += to_sstring("=") + it.second;
                    }
                }
                for (auto it : req->trailing_headers) {
                    rep_content += it.first;
                    if (it.second != "") {
                        rep_content += to_sstring(": ") + it.second;
                    }
                }
                rep->write_body("txt", rep_content);
                return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
            });
        });
    }
};

/*
 * Same handler as above, but without using streams
 *  */
struct echo_string_handler : public handler_base {
    echo_string_handler() = default;
    future<std::unique_ptr<reply>> handle(const sstring& path,
            std::unique_ptr<request> req, std::unique_ptr<reply> rep) override {
        for (auto it : req->chunk_extensions) {
            req->content += it.first;
            if (it.second != "") {
                req->content += to_sstring("=") + it.second;
            }
        }
        for (auto it : req->trailing_headers) {
            req->content += it.first;
            if (it.second != "") {
                req->content += to_sstring(": ") + it.second;
            }
        }
        rep->write_body("txt", req->content);
        return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
    }
};

/*
 * Checks if the server responds to the request equivalent to the concatenation of all req_parts with a reply containing
 * the resp_parts strings, assuming that the content streaming is set to stream and the /test route is handled by handl
 * */
future<> check_http_reply (std::vector<sstring>&& req_parts, std::vector<std::string>&& resp_parts, bool stream, handler_base* handl) {
    return seastar::async([req_parts = std::move(req_parts), resp_parts = std::move(resp_parts), stream, handl] {
        loopback_connection_factory lcf;
        http_server server("test");
        server.set_content_streaming(stream);
        loopback_socket_impl lsi(lcf);
        httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
        future<> client = seastar::async([req_parts = std::move(req_parts), resp_parts = std::move(resp_parts), &lsi] {
            connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
            input_stream<char> input(c_socket.input());
            output_stream<char> output(c_socket.output());

            for (auto& str : req_parts) {
                output.write(std::move(str)).get();
                output.flush().get();
            }
            auto resp = input.read().get0();
            for (auto& str : resp_parts) {
                BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find(std::move(str)), std::string::npos);
            }

            input.close().get();
            output.close().get();
        });

        server._routes.put(GET, "/test", handl);
        server.do_accepts(0).get();

        client.get();
        server.stop().get();
    });
};

SEASTAR_TEST_CASE(test_streamed_content) {
    return seastar::async([] {
        loopback_connection_factory lcf;
        http_server server("test");
        server.set_content_streaming(true);
        loopback_socket_impl lsi(lcf);
        httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
        future<> client = seastar::async([&lsi] {
            connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
            input_stream<char> input(c_socket.input());
            output_stream<char> output(c_socket.output());

            output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 20\r\n\r\n")).get();
            output.flush().get();
            output.write(sstring("1234567890")).get();
            output.flush().get();
            output.write(sstring("1234521345")).get();
            output.flush().get();
            auto resp = input.read().get0();
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("12345678901234521345"), std::string::npos);

            output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 14\r\n\r\n")).get();
            output.flush().get();
            output.write(sstring("second")).get();
            output.flush().get();
            output.write(sstring(" request")).get();
            output.flush().get();
            resp = input.read().get0();
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("second request"), std::string::npos);

            output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\n\r\n")).get();
            output.flush().get();
            // content length assumed to be 0
            resp = input.read().get0();
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);

            output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 3\r\n\r\ntwo"
                                 "GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 8\r\n\r\nrequests")).get();
            output.flush().get();
            resp = input.read().get0();
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("two"), std::string::npos);
            if (std::string(resp.get(), resp.size()).find("requests") == std::string::npos) {
                resp = input.read().get0();
            }
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("requests"), std::string::npos);

            output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n")).get();
            output.write(sstring("a\r\n1234567890\r\n")).get();
            output.write(sstring("a\r\n1234521345\r\n")).get();
            output.write(sstring("0\r\n\r\n")).get();
            output.flush().get();
            resp = input.read().get0();
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("12345678901234521345"), std::string::npos);

            output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n")).get();
            output.write(sstring("B\r\nsecond\r\nreq\r\n")).get();
            output.write(sstring("4\r\nuest\r\n")).get();
            output.write(sstring("0\r\n\r\n")).get();
            output.flush().get();
            resp = input.read().get0();
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("second\r\nrequest"), std::string::npos);

            input.close().get();
            output.close().get();
        });

        auto handler = new echo_stream_handler();
        server._routes.put(GET, "/test", handler);
        server.do_accepts(0).get();

        client.get();
        server.stop().get();
    });
}

SEASTAR_TEST_CASE(test_not_implemented_encoding) {
    return check_http_reply({
        "GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: gzip, chunked\r\n\r\n",
        "a\r\n1234567890\r\n",
        "a\r\n1234521345\r\n",
        "0\r\n\r\n"
    }, {"501 Not Implemented", "Encodings other than \"chunked\" are not implemented (received encoding: \"gzip, chunked\")"}, false, new echo_string_handler());
}

SEASTAR_TEST_CASE(test_string_content) {
    return seastar::async([] {
        loopback_connection_factory lcf;
        http_server server("test");
        loopback_socket_impl lsi(lcf);
        httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
        future<> client = seastar::async([&lsi] {
            connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
            input_stream<char> input(c_socket.input());
            output_stream<char> output(c_socket.output());

            output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 20\r\n\r\n")).get();
            output.flush().get();
            output.write(sstring("1234567890")).get();
            output.flush().get();
            output.write(sstring("1234521345")).get();
            output.flush().get();
            auto resp = input.read().get0();
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("12345678901234521345"), std::string::npos);

            output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 14\r\n\r\n")).get();
            output.flush().get();
            output.write(sstring("second")).get();
            output.flush().get();
            output.write(sstring(" request")).get();
            output.flush().get();
            resp = input.read().get0();
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("second request"), std::string::npos);

            output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\n\r\n")).get();
            output.flush().get();
            // content length assumed to be 0
            resp = input.read().get0();
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);

            output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 3\r\n\r\ntwo"
                                 "GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 8\r\n\r\nrequests")).get();
            output.flush().get();
            resp = input.read().get0();
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("two"), std::string::npos);
            if (std::string(resp.get(), resp.size()).find("requests") == std::string::npos) {
                resp = input.read().get0();
            }
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("requests"), std::string::npos);

            output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n")).get();
            output.write(sstring("a\r\n1234567890\r\n")).get();
            output.write(sstring("a\r\n1234521345\r\n")).get();
            output.write(sstring("0\r\n\r\n")).get();
            output.flush().get();
            resp = input.read().get0();
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("12345678901234521345"), std::string::npos);

            output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n")).get();
            output.write(sstring("B\r\nsecond\r\nreq\r\n")).get();
            output.write(sstring("4\r\nuest\r\n")).get();
            output.write(sstring("0\r\n\r\n")).get();
            output.flush().get();
            resp = input.read().get0();
            BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("second\r\nrequest"), std::string::npos);

            input.close().get();
            output.close().get();
        });

        auto handler = new echo_string_handler();
        server._routes.put(GET, "/test", handler);
        server.do_accepts(0).get();

        client.get();
        server.stop().get();
    });
}

SEASTAR_TEST_CASE(test_full_chunk_format) {
    return check_http_reply({
        "GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n",
        "a;abc-def;hello=world;aaaa\r\n1234567890\r\n",
        "a;a0-!#$%&'*+.^_`|~=\"quoted string obstext\x80\x81\xff quoted_pair: \\a\"\r\n1234521345\r\n",
        "0\r\na:b\r\n~|`_^.+*'&%$#!-0a:  ~!@#$%^&*()_+\x80\x81\xff\r\n  obs fold  \r\n\r\n"
    }, {"12345678901234521345", "abc-def", "hello=world", "aaaa", "a0-!#$%&'*+.^_`|~=quoted string obstext\x80\x81\xff quoted_pair: a",
        "a: b", "~|`_^.+*'&%$#!-0a: ~!@#$%^&*()_+\x80\x81\xff obs fold"
    }, false, new echo_string_handler());
}

SEASTAR_TEST_CASE(test_chunk_extension_parser_fail) {
    return check_http_reply({
        "GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n",
        "7; \r\nnoparse\r\n",
        "0\r\n\r\n"
    }, {"400 Bad Request", "Can't parse chunk size and extensions"}, false, new echo_string_handler());
}

SEASTAR_TEST_CASE(test_trailer_part_parser_fail) {
    return check_http_reply({
        "GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n",
        "8\r\nparsable\r\n",
        "0\r\ngood:header\r\nbad=header\r\n\r\n"
    }, {"400 Bad Request", "Can't parse chunked request trailer"}, false, new echo_string_handler());
}

SEASTAR_TEST_CASE(test_too_long_chunk) {
    return check_http_reply({
        "GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n",
        "a\r\n1234567890\r\n",
        "a\r\n1234521345X\r\n",
        "0\r\n\r\n"
    }, {"400 Bad Request", "The actual chunk length exceeds the specified length"}, true, new echo_stream_handler());
}

SEASTAR_TEST_CASE(test_bad_chunk_length) {
    return check_http_reply({
        "GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n",
        "a\r\n1234567890\r\n",
        "aX\r\n1234521345\r\n",
        "0\r\n\r\n"
    }, {"400 Bad Request", "Can't parse chunk size and extensions"}, true, new echo_stream_handler());
}

SEASTAR_TEST_CASE(case_insensitive_header) {
    std::unique_ptr<seastar::httpd::request> req = std::make_unique<seastar::httpd::request>();
    req->_headers["conTEnt-LengtH"] = "17";
    BOOST_REQUIRE_EQUAL(req->get_header("content-length"), "17");
    BOOST_REQUIRE_EQUAL(req->get_header("Content-Length"), "17");
    BOOST_REQUIRE_EQUAL(req->get_header("cOnTeNT-lEnGTh"), "17");
    return make_ready_future<>();
}

SEASTAR_THREAD_TEST_CASE(multiple_connections) {
    loopback_connection_factory lcf;
    http_server server("test");
    httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
    socket_address addr{ipv4_addr()};

    std::vector<connected_socket> socks;
    // Make sure one shard has two connections pending.
    for (unsigned i = 0; i <= smp::count; ++i) {
        socks.push_back(loopback_socket_impl(lcf).connect(addr, addr).get0());
    }

    server.do_accepts(0).get();
    server.stop().get();
    lcf.destroy_all_shards().get();
}

SEASTAR_TEST_CASE(http_parse_response_status) {
    http_response_parser parser;
    parser.init();
    char r101[] = "HTTP/1.1 101 Switching Protocols\r\n\r\n";
    char r200[] = "HTTP/1.1 200 OK\r\nHost: localhost\r\nhello\r\n";

    parser.parse(r101, r101 + sizeof(r101), r101 + sizeof(r101));
    auto response = parser.get_parsed_response();
    BOOST_REQUIRE_EQUAL(response->_status_code, 101);

    parser.init();
    parser.parse(r200, r200 + sizeof(r200), r200 + sizeof(r200));
    response = parser.get_parsed_response();
    BOOST_REQUIRE_EQUAL(response->_status_code, 200);
    return make_ready_future<>();
}

SEASTAR_TEST_CASE(test_shared_future) {
    shared_promise<json::json_return_type> p;
    auto fut = p.get_shared_future();

    (void)later().then([p = std::move(p)] () mutable {
        p.set_value(json::json_void());
    });

    return std::move(fut).discard_result();
}

ext: cc

max_stars_repo_name: BenPope/seastar

max_issues_repo_name: BenPope/seastar

max_forks_repo_name: BenPope/seastar