diff --git a/CMakeLists.txt b/CMakeLists.txt
index 584d3256e42acd4fd09280094158c6eef5484a80..e2ab0ac60c61e79b307439211cd37db7ac340d4d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -217,11 +217,12 @@ add_subdirectory(components/net)
 add_subdirectory(components/rgbd-sources)
 add_subdirectory(components/control/cpp)
 add_subdirectory(components/operators)
+add_subdirectory(components/streams)
 add_subdirectory(applications/calibration)
-add_subdirectory(applications/groupview)
-add_subdirectory(applications/player)
-add_subdirectory(applications/recorder)
-add_subdirectory(applications/merger)
+#add_subdirectory(applications/groupview)
+#add_subdirectory(applications/player)
+#add_subdirectory(applications/recorder)
+#add_subdirectory(applications/merger)
 
 if (HAVE_AVFORMAT)
 	add_subdirectory(applications/ftl2mkv)
diff --git a/applications/calibration-multi/CMakeLists.txt b/applications/calibration-multi/CMakeLists.txt
index ae1f1bb1698fbcbd1f8de3dc1b279123025d27b0..d14704e2ada4943f18d35cb792c682fa3dd01a25 100644
--- a/applications/calibration-multi/CMakeLists.txt
+++ b/applications/calibration-multi/CMakeLists.txt
@@ -9,4 +9,4 @@ add_executable(ftl-calibrate-multi ${CALIBMULTI})
 
 target_include_directories(ftl-calibrate-multi PRIVATE src)
 
-target_link_libraries(ftl-calibrate-multi ftlcommon ftlnet ftlrgbd Threads::Threads ${OpenCV_LIBS} ${cvsba_LIBS})
+target_link_libraries(ftl-calibrate-multi ftlcommon ftlnet ftlrgbd ftlstreams Threads::Threads ${OpenCV_LIBS} ${cvsba_LIBS})
diff --git a/applications/calibration-multi/src/main.cpp b/applications/calibration-multi/src/main.cpp
index 7c92ea9eaffcf68f1af97c47e1255d95b5a533cc..b8c62e758c509e4448a361b83ae2d294c5f5c8f5 100644
--- a/applications/calibration-multi/src/main.cpp
+++ b/applications/calibration-multi/src/main.cpp
@@ -1,10 +1,14 @@
 #include <loguru.hpp>
-
+#include <ftl/threads.hpp>
 #include <ftl/configuration.hpp>
 #include <ftl/net/universe.hpp>
 #include <ftl/rgbd/source.hpp>
 #include <ftl/rgbd/group.hpp>
 
+#include <ftl/master.hpp>
+#include <ftl/streams/receiver.hpp>
+#include <ftl/streams/netstream.hpp>
+
 #include <opencv2/core.hpp>
 #include <opencv2/aruco.hpp>
 #include <opencv2/core/eigen.hpp>
@@ -44,95 +48,21 @@ Mat createCameraMatrix(const ftl::rgbd::Camera &parameters) {
 	return m;
 }
 
-void to_json(nlohmann::json &json, map<string, Eigen::Matrix4d> &transformations) {
-	for (auto &item : transformations) {
-		auto val = nlohmann::json::array();
-		for(size_t i = 0; i < 16; i++) { val.push_back((float) item.second.data()[i]); }
-		json[item.first] = val;
-	}
-}
-
-// FileStorage allows only alphanumeric keys (code below does not work with URIs)
-
-bool saveRegistration(const string &ofile, const map<string, Mat> &data) {
-	cv::FileStorage fs(ofile, cv::FileStorage::WRITE);
-	if (!fs.isOpened()) return false;
-	for (auto &item : data) { fs << item.first << item.second; }
-	fs.release();
-	return true;
-}
-
-bool saveRegistration(const string &ofile, const map<string, Eigen::Matrix4d> &data) {
-	map<string, Mat> _data;
-	for (auto &item : data) {
-		Mat M;
-		cv::eigen2cv(item.second, M);
-		_data[item.first] = M; 
-	}
-	return saveRegistration(ofile, _data);
-}
-
-bool loadRegistration(const string &ifile, map<string, Mat> &data) {
-	cv::FileStorage fs(ifile, cv::FileStorage::READ);
-	if (!fs.isOpened()) return false;
-	for(cv::FileNodeIterator fit = fs.getFirstTopLevelNode().begin();
-		fit != fs.getFirstTopLevelNode().end();
-		++fit)
-	{
-		data[(*fit).name()] = (*fit).mat();
-	}
-	fs.release();
-	return true; // TODO errors?
-}
-
-bool loadRegistration(const string &ifile, map<string, Eigen::Matrix4d> &data) {
-	map<string, Mat> _data;
-	if (!loadRegistration(ifile, _data)) return false;
-	for (auto &item : _data) {
-		Eigen::Matrix4d M;
-		cv::cv2eigen(item.second, M);
-		data[item.first] = M;
-	}
-	return true;
-}
-
-//
-
-bool saveIntrinsics(const string &ofile, const vector<Mat> &M, const Size &size) {
-	vector<Mat> D;
-	{
-		cv::FileStorage fs(ofile, cv::FileStorage::READ);
-		fs["D"] >> D;
-		fs.release();
-	}
-	{
-		cv::FileStorage fs(ofile, cv::FileStorage::WRITE);
-		if (fs.isOpened()) {
-			fs << "resolution" << size;
-			fs << "K" << M << "D" << D;
-			fs.release();
-			return true;
-		}
-		else {
-			LOG(ERROR) << "Error: can not save the intrinsic parameters to '" << ofile << "'";
-		}
-		return false;
-	}
-}
+struct CalibrationParams {
+	string output_path;
+	string registration_file;
+	vector<size_t> idx_cameras;
+	bool save_extrinsic = true;
+	bool save_intrinsic = false;
+	bool optimize_intrinsic = false;
+	int reference_camera = -1;
+	double alpha = 0.0;
+	Size size;
+};
 
-bool saveExtrinsics(const string &ofile, Mat &R, Mat &T, Mat &R1, Mat &R2, Mat &P1, Mat &P2, Mat &Q) {
-	cv::FileStorage fs;
-	fs.open(ofile, cv::FileStorage::WRITE);
-	if (fs.isOpened()) {
-		fs << "R" << R << "T" << T << "R1" << R1 << "R2" << R2 << "P1"
-			<< P1 << "P2" << P2 << "Q" << Q;
-		fs.release();
-		return true;
-	} else {
-		LOG(ERROR) << "Error: can not save the extrinsic parameters";
-	}
-	return false;
-}
+////////////////////////////////////////////////////////////////////////////////
+// Visualization
+////////////////////////////////////////////////////////////////////////////////
 
 void stack(const vector<Mat> &img, Mat &out, const int rows, const int cols) {
 	Size size = img[0].size();
@@ -156,13 +86,6 @@ void stack(const vector<Mat> &img, Mat &out) {
 	stack(img, out, rows, cols);
 }
 
-string time_now_string() {
-	char timestamp[18];
-	std::time_t t=std::time(NULL);
-	std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
-	return string(timestamp);
-}
-
 void visualizeCalibration(	MultiCameraCalibrationNew &calib, Mat &out,
 				 			vector<Mat> &rgb, const vector<Mat> &map1,
 							const vector<Mat> &map2, const vector<cv::Rect> &roi)
@@ -192,21 +115,57 @@ void visualizeCalibration(	MultiCameraCalibrationNew &calib, Mat &out,
 	stack(rgb, out);
 }
 
-struct CalibrationParams {
-	string output_path;
-	string registration_file;
-	vector<size_t> idx_cameras;
-	bool save_extrinsic = true;
-	bool save_intrinsic = false;
-	bool optimize_intrinsic = false;
-	int reference_camera = -1;
-	double alpha = 0.0;
-	Size size;
-};
+////////////////////////////////////////////////////////////////////////////////
+// RPC
+////////////////////////////////////////////////////////////////////////////////
 
-void calibrate(	MultiCameraCalibrationNew &calib, vector<string> &uri_cameras,
-				const CalibrationParams &params, vector<Mat> &map1, vector<Mat> &map2, vector<cv::Rect> &roi)
-{
+bool setRectifyRPC(ftl::net::Universe* net, ftl::stream::Net* nstream, bool enabled) {
+	return net->call<bool>(nstream->getPeer(), "set_rectify", enabled);
+}
+
+bool setIntrinsicsRPC(ftl::net::Universe* net, ftl::stream::Net* nstream, Size size, vector<Mat> K, vector<Mat> D) {
+	Mat K0 = K[0].t();
+	Mat K1 = K[1].t();
+
+	return net->call<bool>(	nstream->getPeer(), "set_intrinsics",
+							vector<int>{size.width, size.height},
+							vector<double>(K0.begin<double>(), K0.end<double>()),
+							vector<double>(D[0].begin<double>(), D[0].end<double>()),
+							vector<double>(K1.begin<double>(), K1.end<double>()),
+							vector<double>(D[1].begin<double>(), D[1].end<double>())
+	);
+}
+
+bool setExtrinsicsRPC(ftl::net::Universe* net, ftl::stream::Net* nstream, Mat R, Mat t) {
+	Mat rvec;
+	cv::Rodrigues(R, rvec);
+	return net->call<bool>(	nstream->getPeer(), "set_extrinsics",
+							vector<double>(rvec.begin<double>(), rvec.end<double>()),
+							vector<double>(t.begin<double>(), t.end<double>())
+	);
+}
+
+bool setPoseRPC(ftl::net::Universe* net, ftl::stream::Net* nstream, Mat pose) {
+	Mat P = pose.t();
+	return net->call<bool>(	nstream->getPeer(), "set_pose", 
+							vector<double>(P.begin<double>(), P.end<double>()));
+}
+
+bool saveCalibrationRPC(ftl::net::Universe* net, ftl::stream::Net* nstream) {
+	return net->call<bool>(nstream->getPeer(), "save_calibration");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+/* run calibration and perform RPC to update calibration on nodes */
+
+void calibrateRPC(	ftl::net::Universe* net,
+					MultiCameraCalibrationNew &calib,
+					const CalibrationParams &params,
+					vector<ftl::stream::Net*> &nstreams,
+					vector<Mat> &map1,
+					vector<Mat> &map2,
+					vector<cv::Rect> &roi) {
 	int reference_camera = params.reference_camera;
 	if (params.reference_camera < 0) {
 		reference_camera = calib.getOptimalReferenceCamera();
@@ -246,44 +205,23 @@ void calibrate(	MultiCameraCalibrationNew &calib, vector<string> &uri_cameras,
 		Rt_out[c] = getMat4x4(R[c], t[c]) * getMat4x4(R1, _t).inv();
 		Rt_out[c + 1] = getMat4x4(R[c + 1], t[c + 1]) * getMat4x4(R2, _t).inv();
 
-		{
-			string node_name;
-			size_t pos1 = uri_cameras[c/2].find("node");
-			size_t pos2 = uri_cameras[c/2].find("#", pos1);
-			node_name = uri_cameras[c/2].substr(pos1, pos2 - pos1);
-			
-			if (params.save_extrinsic)
-			{
-				// TODO:	only R and T required when rectification performed
-				//			on vision node. Consider saving extrinsic global
-				//			for node as well?
-				saveExtrinsics(	params.output_path + node_name + "-extrinsic.yml",
-								R_c1c2, T_c1c2, R1, R2, P1, P2, Q
-				);
-				LOG(INFO) << "Saved: " << params.output_path + node_name + "-extrinsic.yml";
+		auto *nstream = nstreams[c/2];
+		while(true) {
+			try {
+				/*setIntrinsicsRPC(net, nstream,
+					params.size,
+					vector<Mat>{calib.getCameraMat(c), calib.getCameraMat(c+1)},
+					vector<Mat>{calib.getDistCoeffs(c), calib.getDistCoeffs(c+1)}
+				);*/
+				setExtrinsicsRPC(net, nstream, R_c1c2, T_c1c2);
+				setPoseRPC(net, nstream, Rt_out[c]);
+				saveCalibrationRPC(net, nstream);
+				LOG(INFO) << "CALIBRATION SENT";
+				break;
 			}
-			else
-			{
-				Mat rvec;
-				cv::Rodrigues(R_c1c2, rvec);
-				LOG(INFO) << "From camera " << c << " to " << c + 1;
-				LOG(INFO) << "rotation:    " << rvec.t();
-				LOG(INFO) << "translation: " << T_c1c2.t();
-			}
-
-			if (params.save_intrinsic)
-			{
-				saveIntrinsics(
-					params.output_path + node_name + "-intrinsic.yml",
-					{ calib.getCameraMat(c), calib.getCameraMat(c + 1) },
-					params.size
-				);
-				LOG(INFO) << "Saved: " << params.output_path + node_name + "-intrinsic.yml";
-			}
-			else if (params.optimize_intrinsic)
-			{
-				LOG(INFO) << "K1:\n" << K1;
-				LOG(INFO) << "K2:\n" << K2;
+			catch (...) {
+				LOG(ERROR) << "RPC failed!";
+				sleep(1);
 			}
 		}
 
@@ -295,88 +233,6 @@ void calibrate(	MultiCameraCalibrationNew &calib, vector<string> &uri_cameras,
 		cv::initUndistortRectifyMap(K1, D1, R1, P1, params.size, CV_16SC2, map1[c], map2[c]);
 		cv::initUndistortRectifyMap(K2, D2, R2, P2, params.size, CV_16SC2, map1[c + 1], map2[c + 1]);
 	}
-
-	{
-		map<string, Eigen::Matrix4d> out;
-		for (size_t i = 0; i < n_cameras; i += 2) {
-			Eigen::Matrix4d M_eigen;
-			Mat M_cv = Rt_out[i];
-			cv::cv2eigen(M_cv, M_eigen);
-			out[uri_cameras[i/2]] = M_eigen;
-		}
-		
-		nlohmann::json out_json;
-		to_json(out_json, out);
-		if (params.save_extrinsic) {
-			std::ofstream file_out(params.registration_file);
-			file_out << out_json;
-		}
-		else {
-			LOG(INFO) << "Registration not saved to file";
-			LOG(INFO) << out_json;
-		}
-	}
-}
-
-void calibrateFromPath(	const string &path,
-						const string &filename,
-						CalibrationParams &params,
-						bool visualize=false)
-{
-	size_t reference_camera = 0;
-	auto calib = MultiCameraCalibrationNew(0, reference_camera, Size(0, 0), CalibrationTarget(0.250));
-	
-	vector<string> uri_cameras;
-	cv::FileStorage fs(path + filename, cv::FileStorage::READ);
-	fs["uri"] >> uri_cameras;
-	fs["resolution"] >> params.size;
-
-	//params.idx_cameras = {2, 3};//{0, 1, 4, 5, 6, 7, 8, 9, 10, 11};
-	params.idx_cameras.resize(uri_cameras.size() * 2);
-	std::iota(params.idx_cameras.begin(), params.idx_cameras.end(), 0);
-
-	calib.loadInput(path + filename, params.idx_cameras);
-	
-	vector<Mat> map1, map2;
-	vector<cv::Rect> roi;
-	calibrate(calib, uri_cameras, params, map1, map2, roi);
-
-	if (!visualize) return;
-
-	vector<Scalar> colors = {
-		Scalar(64, 64, 255),
-		Scalar(64, 64, 255),
-		Scalar(64, 255, 64),
-		Scalar(64, 255, 64),
-	};
-	vector<int> markers = {cv::MARKER_SQUARE, cv::MARKER_DIAMOND};
-
-	Mat out;
-	size_t n_cameras = calib.getCamerasCount();
-	vector<Mat> rgb(n_cameras);
-	size_t i = 0;
-	while(ftl::running) {
-		for (size_t c = 0; c < n_cameras; c++) {
-			rgb[c] = cv::imread(path + std::to_string(params.idx_cameras[c]) + "_" + std::to_string(i) + ".jpg");
-			for (size_t j = 0; j < rgb.size(); j++) {
-				vector<Point2d> points;
-				// TODO: indexing incorrect if all cameras used (see also loadInput)
-				calib.projectPointsOptimized(c, i, points); // project BA point to image
-
-				for (Point2d &p : points) {
-					cv::drawMarker(rgb[c], cv::Point2i(p), colors[j % colors.size()], markers[j % markers.size()], 10 + 3 * j, 1);
-				}
-			}
-		}
-		
-		visualizeCalibration(calib, out, rgb, map1, map2, roi);	
-		cv::namedWindow("Calibration", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL);
-		cv::imshow("Calibration", out);
-
-		i = (i + 1) % calib.getViewsCount();
-		
-		if (cv::waitKey(50) != -1) { ftl::running = false; }
-	}
 }
 
 void runCameraCalibration(	ftl::Configurable* root,
@@ -386,59 +242,91 @@ void runCameraCalibration(	ftl::Configurable* root,
 							CalibrationParams &params)
 {
 	Universe *net = ftl::create<Universe>(root, "net");
+	ftl::ctrl::Master ctrl(root, net);
+
 	net->start();
 	net->waitConnections();
-
-	vector<Source*> sources = ftl::createArray<Source>(root, "sources", net);
 	
-	const size_t n_sources = sources.size();
+	ftl::stream::Muxer *stream = ftl::create<ftl::stream::Muxer>(root, "muxstream");
+	ftl::stream::Receiver *gen = ftl::create<ftl::stream::Receiver>(root, "receiver");
+	gen->setStream(stream);
+	auto stream_uris = net->findAll<std::string>("list_streams");
+	std::vector<ftl::stream::Net*> nstreams;
+
+	int count = 0;
+	for (auto &s : stream_uris) {
+		LOG(INFO) << " --- found stream: " << s;
+		auto *nstream = ftl::create<ftl::stream::Net>(stream, std::to_string(count), net);
+		nstream->set("uri", s);
+		nstreams.push_back(nstream);
+		stream->add(nstream);
+		++count;
+	}
+	
+	const size_t n_sources = nstreams.size();
 	const size_t n_cameras = n_sources * 2;
 	size_t reference_camera = 0;
-	
-	ftl::rgbd::Group group;
-	for (Source* src : sources) {
-		src->setChannel(Channel::Right);
-		group.addSource(src);
-	}
 
 	std::mutex mutex;
 	std::atomic<bool> new_frames = false;
-	vector<Mat> rgb(n_cameras), rgb_new(n_cameras);
-	
-	ftl::timer::start(false);
+	vector<Mat> rgb_(n_cameras), rgb_new(n_cameras);
+	vector<Mat> camera_parameters(n_cameras);
+	Size res;
 
-	group.sync([&mutex, &new_frames, &rgb_new](ftl::rgbd::FrameSet &frames) {
-		mutex.lock();
+	gen->onFrameSet([stream, &mutex, &new_frames, &rgb_new, &camera_parameters, &res](ftl::rgbd::FrameSet &fs) {
+		stream->select(fs.id, Channel::Left + Channel::Right);
+		UNIQUE_LOCK(mutex, CALLBACK);
 		bool good = true;
-		for (size_t i = 0; i < frames.sources.size(); i ++) {
-			frames.frames[i].download(Channel::Left+Channel::Right);
-
-			if (frames.frames[i].get<cv::Mat>(Channel::Left).empty()) good = false;
-			if (frames.frames[i].get<cv::Mat>(Channel::Right).empty()) good = false;
-			if (frames.frames[i].get<cv::Mat>(Channel::Left).channels() != 3) good = false; // ASSERT
-			if (frames.frames[i].get<cv::Mat>(Channel::Right).channels() != 3) good = false;
-			if (!good) break;
-			cv::swap(frames.frames[i].get<cv::Mat>(Channel::Left), rgb_new[2 * i]);
-			cv::swap(frames.frames[i].get<cv::Mat>(Channel::Right), rgb_new[2 * i + 1]);
+		try {
+			for (size_t i = 0; i < fs.frames.size(); i ++) {	
+				fs.frames[i].download(Channel::Left+Channel::Right);
+				
+				Mat left = fs.frames[i].get<Mat>(Channel::Left);
+				cv::cvtColor(left, rgb_new[2*i], cv::COLOR_BGRA2BGR);
+				Mat right = fs.frames[i].get<Mat>(Channel::Right);
+				cv::cvtColor(right, rgb_new[2*i+1], cv::COLOR_BGRA2BGR);
+
+				if (left.empty()) good = false;
+				if (right.empty()) good = false;
+				if (!good) break;
+
+				camera_parameters[2*i] = createCameraMatrix(fs.frames[i].getLeftCamera());
+				camera_parameters[2*i+1] = createCameraMatrix(fs.frames[i].getRightCamera());
+				if (res.empty()) res = rgb_new[2*i].size();
+			}
+		}
+		catch (...) {
+			good = false;
 		}
-
 		new_frames = good;
-		mutex.unlock();
 		return true;
 	});
 
-	for (auto &source : sources) {
-		while (!source->isReady()) {
-			std::this_thread::sleep_for(std::chrono::milliseconds(100));
+	stream->begin();
+	ftl::timer::start(false);
+
+	while(true) {
+		if (!res.empty()) {
+			params.size = res;
+			LOG(INFO) << "Camera resolution: " << params.size;
+			break;
 		}
+		sleep(1);
 	}
 
-	{
-		auto camera = sources[0]->parameters();
-		params.size = Size(camera.width, camera.height);
-		LOG(INFO) << "Camera resolution: " << params.size;
-	}
+	for (auto *nstream: nstreams) {
+		bool res = false;
+		try { res = setRectifyRPC(net, nstream, false); }
+		catch (...) {}
 
+		if (!res) {
+			LOG(ERROR) << "set_rectify() failed for " << *(nstream->get<string>("uri"));
+		}
+		else {
+			LOG(INFO) << "rectification disabled for " << *(nstream->get<string>("uri"));
+		}
+	}
+	
 	params.idx_cameras.resize(n_cameras);
 	std::iota(params.idx_cameras.begin(), params.idx_cameras.end(), 0);
 
@@ -447,40 +335,25 @@ void runCameraCalibration(	ftl::Configurable* root,
 											params.size, CalibrationTarget(0.250)
 	);
 
-	for (size_t i = 0; i < n_sources; i++) {
-		auto camera_r = sources[i]->parameters(Channel::Right);
-		auto camera_l = sources[i]->parameters();
-
-		CHECK(params.size == Size(camera_r.width, camera_r.height));
-		CHECK(params.size == Size(camera_l.width, camera_l.height));
-		
-		Mat K;
-		K = createCameraMatrix(camera_r);
-		LOG(INFO) << "K[" << 2 * i + 1 << "] = \n" << K;
-		calib.setCameraParameters(2 * i + 1, K);
-
-		K = createCameraMatrix(camera_l);
-		LOG(INFO) << "K[" << 2 * i << "] = \n" << K;
-		calib.setCameraParameters(2 * i, K);
-	}
-	
 	int iter = 0;
 	Mat show;
 
 	vector<int> visible;
 	vector<vector<Point2d>> points(n_cameras);
 
+	vector<Mat> rgb(n_cameras);
+	sleep(3);
+
 	while(calib.getMinVisibility() < n_views) {
 		cv::waitKey(10);
-		while (!new_frames) {
-			for (auto src : sources) { src->grab(30); }
-			cv::waitKey(10);
+		
+		while (new_frames) {
+			UNIQUE_LOCK(mutex, LOCK);
+			if (new_frames) rgb.swap(rgb_new);
+			new_frames = false;
 		}
-
-		mutex.lock();
-		rgb.swap(rgb_new);
-		new_frames = false;
-		mutex.unlock();
+		
+		for (Mat &im : rgb) { CHECK(!im.empty()); }
 
 		visible.clear();
 		int n_found = findCorrespondingPoints(rgb, points, visible);
@@ -521,41 +394,39 @@ void runCameraCalibration(	ftl::Configurable* root,
 	}
 	cv::destroyWindow("Cameras");
 
-	vector<string> uri;
-	for (size_t i = 0; i < n_sources; i++) {
-		uri.push_back(sources[i]->getURI());
-	}
-
-	if (save_input) {
-		cv::FileStorage fs(path + filename, cv::FileStorage::WRITE);
-		fs << "uri" << uri;
-		calib.saveInput(fs);
-		fs.release();
+	for (size_t i = 0; i < camera_parameters.size(); i++) {
+		//CHECK(params.size == Size(camera_r.width, camera_r.height));
+		//CHECK(params.size == Size(camera_l.width, camera_l.height));
+		
+		LOG(INFO) << "K[" << i << "] = \n" << camera_parameters[i];
+		calib.setCameraParameters(i, camera_parameters[i]);
 	}
-
+	
 	Mat out;
 	vector<Mat> map1, map2;
 	vector<cv::Rect> roi;
 	vector<size_t> idx;
-	calibrate(calib, uri, params, map1, map2, roi);
+	calibrateRPC(net, calib, params, nstreams, map1, map2, roi);
 
 	// visualize
 	while(ftl::running) {
 		cv::waitKey(10);
 		while (!new_frames) {
-			for (auto src : sources) { src->grab(30); }
 			if (cv::waitKey(50) != -1) { ftl::running = false; }
 		}
 
-		mutex.lock();
-		rgb.swap(rgb_new);
-		new_frames = false;
-		mutex.unlock();
+		{
+			UNIQUE_LOCK(mutex, LOCK)
+			rgb.swap(rgb_new);
+			new_frames = false;
+			mutex.unlock();
+		}
 
 		visualizeCalibration(calib, out, rgb, map1, map2, roi);
 		cv::namedWindow("Calibration", cv::WINDOW_KEEPRATIO | cv::WINDOW_NORMAL);
 		cv::imshow("Calibration", out);
 	}
+	
 }
 
 int main(int argc, char **argv) {
@@ -595,25 +466,23 @@ int main(int argc, char **argv) {
 	params.reference_camera = ref_camera;
 	
 	LOG(INFO)	<< "\n"
-				<< "\nIMPORTANT: Remeber to set \"use_intrinsics\" to false for nodes!"
 				<< "\n"
-				<< "\n                save_input: " << (int) save_input
-				<< "\n                load_input: " << (int) load_input
-				<< "\n            save_extrinsic: " << (int) save_extrinsic
-				<< "\n            save_intrinsic: " << (int) save_intrinsic
+//				<< "\n                save_input: " << (int) save_input
+//				<< "\n                load_input: " << (int) load_input
+//				<< "\n            save_extrinsic: " << (int) save_extrinsic
+//				<< "\n            save_intrinsic: " << (int) save_intrinsic
 				<< "\n        optimize_intrinsic: " << (int) optimize_intrinsic
-				<< "\n      calibration_data_dir: " << calibration_data_dir
-				<< "\n     calibration_data_file: " << calibration_data_file
+//				<< "\n      calibration_data_dir: " << calibration_data_dir
+//				<< "\n     calibration_data_file: " << calibration_data_file
 				<< "\n               min_visible: " << min_visible
 				<< "\n                   n_views: " << n_views
 				<< "\n          reference_camera: " << ref_camera << (ref_camera != -1 ? "" : " (automatic)")
-				<< "\n         registration_file: " << registration_file
-				<< "\n          output_directory: " << output_directory
+//				<< "\n         registration_file: " << registration_file
+//				<< "\n          output_directory: " << output_directory
 				<< "\n";
 
 	if (load_input) {
-		vector<size_t> idx = {};
-		calibrateFromPath(calibration_data_dir, calibration_data_file, params, true);
+		LOG(FATAL) << "TODO";
 	}
 	else {
 		runCameraCalibration(root, n_views, min_visible, calibration_data_dir, calibration_data_file, save_input, params);
diff --git a/applications/calibration/src/lens.cpp b/applications/calibration/src/lens.cpp
index b69b26cc6e4cfec9dab04d337d75b21721d2fbae..c0c44bbe0cd387545b3f3f1980abf9fa6f5e701c 100644
--- a/applications/calibration/src/lens.cpp
+++ b/applications/calibration/src/lens.cpp
@@ -39,7 +39,7 @@ void ftl::calibration::intrinsic(map<string, string> &opt) {
 	const int delay = getOptionInt(opt, "delay", 1000);
 	const double aperture_width = getOptionDouble(opt, "aperture_width", 6.2);
 	const double aperture_height = getOptionDouble(opt, "aperture_height", 4.6);
-	const string filename_intrinsics = getOptionString(opt, "profile", FTL_LOCAL_CONFIG_ROOT "/intrinsics.yml");
+	const string filename_intrinsics = getOptionString(opt, "profile", FTL_LOCAL_CONFIG_ROOT "/calibration.yml");
 	CalibrationChessboard calib(opt);
 	bool use_guess = getOptionInt(opt, "use_guess", 1);
 	//bool use_guess_distortion = getOptionInt(opt, "use_guess_distortion", 0);
@@ -53,7 +53,8 @@ void ftl::calibration::intrinsic(map<string, string> &opt) {
 	LOG(INFO) << "                 delay: " << delay;
 	LOG(INFO) << "        aperture_width: " << aperture_width;
 	LOG(INFO) << "       aperture_height: " << aperture_height;
-	LOG(INFO) << "             use_guess: " << use_guess;
+	LOG(INFO) << "             use_guess: " << use_guess << "\n";
+	LOG(WARNING) << "WARNING: This application overwrites existing files and does not previous values!";
 	//LOG(INFO) << "  use_guess_distortion: " << use_guess_distortion;
 
 	LOG(INFO) << "-----------------------------------";
diff --git a/applications/ftl2mkv/src/main.cpp b/applications/ftl2mkv/src/main.cpp
index 598e372ca524d33e358c1f312fdeba5429fa0ea8..3e6b0e302bd7411f492a3a6eb9fbdc708dbe6fba 100644
--- a/applications/ftl2mkv/src/main.cpp
+++ b/applications/ftl2mkv/src/main.cpp
@@ -181,7 +181,7 @@ int main(int argc, char **argv) {
 
 			bool keyframe = false;
 			if (pkt.codec == codec_t::HEVC) {
-				if (ftl::codecs::hevc::isIFrame(pkt.data)) {
+				if (ftl::codecs::hevc::isIFrame(pkt.data.data(), pkt.data.size())) {
 					seen_key[spkt.streamID] = true;
 					keyframe = true;
 				}
diff --git a/applications/groupview/src/main.cpp b/applications/groupview/src/main.cpp
index be99d857562bfb48ec120cccccf0737ff2d0b210..f9d763fb607d595f67f089220921f7e92f51ffff 100644
--- a/applications/groupview/src/main.cpp
+++ b/applications/groupview/src/main.cpp
@@ -88,7 +88,7 @@ void modeLeftRight(ftl::Configurable *root) {
 	std::atomic<bool> new_frames = false;
 	vector<Mat> rgb(n_cameras), rgb_new(n_cameras);
 	
-	group.sync([&mutex, &new_frames, &rgb_new](ftl::rgbd::FrameSet &frames) {
+	group.onFrameSet([&mutex, &new_frames, &rgb_new](ftl::rgbd::FrameSet &frames) {
 		mutex.lock();
 		bool good = true;
 		for (size_t i = 0; i < frames.frames.size(); i ++) {
@@ -179,7 +179,7 @@ void modeFrame(ftl::Configurable *root, int frames=1) {
 	vector<cv::Mat> depth(sources.size());
 	MUTEX mtx;
 
-	group.sync([&grab,&rgb,&depth,&mtx](ftl::rgbd::FrameSet &fs) {
+	group.onFrameSet([&grab,&rgb,&depth,&mtx](ftl::rgbd::FrameSet &fs) {
 		UNIQUE_LOCK(mtx, lk);
 		//LOG(INFO) << "Complete set: " << fs.timestamp;
 		if (!ftl::running) { return false; }
diff --git a/applications/gui/CMakeLists.txt b/applications/gui/CMakeLists.txt
index 9764984a7b430c4bc256918aac2b39e356a1ee16..65d7964d5cf891e2b288251a43c4c8936f4199e0 100644
--- a/applications/gui/CMakeLists.txt
+++ b/applications/gui/CMakeLists.txt
@@ -32,6 +32,6 @@ target_include_directories(ftl-gui PUBLIC
 #endif()
 
 #target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include)
-target_link_libraries(ftl-gui ftlcommon ftlctrl ftlrgbd Threads::Threads ${OpenCV_LIBS} ${OPENVR_LIBRARIES} glog::glog ftlnet nanogui GL)
+target_link_libraries(ftl-gui ftlcommon ftlctrl ftlrgbd ftlstreams ftlrender Threads::Threads ${OpenCV_LIBS} ${OPENVR_LIBRARIES} glog::glog ftlnet nanogui GL)
 
 
diff --git a/applications/gui/src/camera.cpp b/applications/gui/src/camera.cpp
index 3259bfa336fc62603d240488bf2db90e21c2ef63..3924410d4c4c1056634c01fe19efd9123c80ffe2 100644
--- a/applications/gui/src/camera.cpp
+++ b/applications/gui/src/camera.cpp
@@ -131,7 +131,7 @@ static Eigen::Affine3d create_rotation_matrix(float ax, float ay, float az) {
 	return rz * rx * ry;
 }
 
-ftl::gui::Camera::Camera(ftl::gui::Screen *screen, ftl::rgbd::Source *src) : screen_(screen), src_(src) {
+ftl::gui::Camera::Camera(ftl::gui::Screen *screen, int fsid, int fid, ftl::codecs::Channel c) : screen_(screen), fsid_(fsid), fid_(fid), channel_(c),channels_(0u) {
 	eye_ = Eigen::Vector3d(0.0f, 0.0f, 0.0f);
 	neye_ = Eigen::Vector4d(0.0f, 0.0f, 0.0f, 0.0f);
 	rotmat_.setIdentity();
@@ -140,24 +140,34 @@ ftl::gui::Camera::Camera(ftl::gui::Screen *screen, ftl::rgbd::Source *src) : scr
 	sdepth_ = false;
 	ftime_ = (float)glfwGetTime();
 	pause_ = false;
-	fileout_ = new std::ofstream();
-	writer_ = new ftl::codecs::Writer(*fileout_);
+	//fileout_ = new std::ofstream();
+	/*writer_ = new ftl::codecs::Writer(*fileout_);
 	recorder_ = std::function([this](ftl::rgbd::Source *src, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
 		ftl::codecs::StreamPacket s = spkt;
 		writer_->write(s, pkt);
-	});
+	});*/
+	recording_ = false;
 
-	channel_ = Channel::Left;
+#ifdef HAVE_OPENVR
+	vr_mode_ = false;
+#endif
 
-	channels_ += Channel::Left;
-	channels_ += Channel::Depth;
+	//channel_ = Channel::Left;
 
-	// Create pose window...
-	posewin_ = new PoseWindow(screen, src_->getURI());
-	posewin_->setTheme(screen->windowtheme);
-	posewin_->setVisible(false);
+	channels_ += c;
+	//channels_ += Channel::Depth;
+	width_ = 0;
+	height_ = 0;
 
-	src->setCallback([this](int64_t ts, ftl::rgbd::Frame &frame) {
+	// Create pose window...
+	//posewin_ = new PoseWindow(screen, src_->getURI());
+	//posewin_->setTheme(screen->windowtheme);
+	//posewin_->setVisible(false);
+	posewin_ = nullptr;
+	renderer_ = nullptr;
+	record_stream_ = nullptr;
+
+	/*src->setCallback([this](int64_t ts, ftl::rgbd::Frame &frame) {
 		UNIQUE_LOCK(mutex_, lk);
 
 		auto &channel1 = frame.get<GpuMat>(Channel::Colour);
@@ -173,16 +183,120 @@ ftl::gui::Camera::Camera(ftl::gui::Screen *screen, ftl::rgbd::Source *src) : scr
 			channel2.download(im2_);
 			cv::flip(im2_, im2_, 0);
 		}
-	});
+	});*/
+
+	auto *host = screen->root();
+
+	// Is virtual camera?
+	if (fid == 255) {
+		state_.getLeft().width = host->value("width", 1280);
+		state_.getLeft().height = host->value("height", 720);
+		state_.getLeft().fx = host->value("focal", 700.0f);
+		state_.getLeft().fy = state_.getLeft().fx;
+		state_.getLeft().cx = -(double)state_.getLeft().width / 2.0;
+		state_.getLeft().cy = -(double)state_.getLeft().height / 2.0;
+		state_.getLeft().minDepth = host->value("minDepth", 0.1f);
+		state_.getLeft().maxDepth = host->value("maxDepth", 20.0f);
+		state_.getLeft().doffs = 0;
+		state_.getLeft().baseline = host->value("baseline", 0.05f);
+
+		state_.getRight().width = host->value("width", 1280);
+		state_.getRight().height = host->value("height", 720);
+		state_.getRight().fx = host->value("focal_right", 700.0f);
+		state_.getRight().fy = state_.getRight().fx;
+		state_.getRight().cx = host->value("centre_x_right", -(double)state_.getLeft().width / 2.0);
+		state_.getRight().cy = host->value("centre_y_right", -(double)state_.getLeft().height / 2.0);
+		state_.getRight().minDepth = host->value("minDepth", 0.1f);
+		state_.getRight().maxDepth = host->value("maxDepth", 20.0f);
+		state_.getRight().doffs = 0;
+		state_.getRight().baseline = host->value("baseline", 0.05f);
+
+		Eigen::Matrix4d pose;
+		pose.setIdentity();
+		state_.setPose(pose);
+	}
 }
 
 ftl::gui::Camera::~Camera() {
-	delete writer_;
-	delete fileout_;
+	//delete writer_;
+	//delete fileout_;
+}
+
+void ftl::gui::Camera::draw(ftl::rgbd::FrameSet &fs) {
+	UNIQUE_LOCK(mutex_, lk);
+	_draw(fs);
 }
 
-ftl::rgbd::Source *ftl::gui::Camera::source() {
-	return src_;
+void ftl::gui::Camera::_draw(ftl::rgbd::FrameSet &fs) {
+	frame_.reset();
+	frame_.setOrigin(&state_);
+	if (!renderer_) renderer_ = ftl::create<ftl::render::Triangular>(screen_->root(), "vcam1");
+	Eigen::Matrix4d t;
+	t.setIdentity();
+	renderer_->render(fs, frame_, channel_, t);
+	_downloadFrames(&frame_);
+
+	if (record_stream_ && record_stream_->active()) {
+		// TODO: Allow custom channel selection
+		ftl::rgbd::FrameSet fs2;
+		auto &f = fs2.frames.emplace_back();
+		fs2.count = 1;
+		fs2.mask = 1;
+		fs2.stale = false;
+		frame_.swapTo(Channels<0>(Channel::Colour), f);  // Channel::Colour + Channel::Depth
+		fs2.timestamp = fs.timestamp;
+		fs2.id = 0;
+		record_sender_->post(fs2);
+		record_stream_->select(0, Channels<0>(Channel::Colour));
+		f.swapTo(Channels<0>(Channel::Colour), frame_);
+	}
+}
+
+void ftl::gui::Camera::_downloadFrames(ftl::rgbd::Frame *frame) {
+	if (!frame) return;
+
+	auto &channel1 = frame->get<GpuMat>(Channel::Colour);
+	im1_.create(channel1.size(), channel1.type());
+	channel1.download(im1_);
+
+	// OpenGL (0,0) bottom left
+	cv::flip(im1_, im1_, 0);
+
+	width_ = im1_.cols;
+	height_ = im1_.rows;
+
+	if (channel_ != Channel::Colour && channel_ != Channel::None && frame->hasChannel(channel_)) {
+		auto &channel2 = frame->get<GpuMat>(channel_);
+		im2_.create(channel2.size(), channel2.type());
+		channel2.download(im2_);
+		//LOG(INFO) << "Have channel2: " << im2_.type() << ", " << im2_.size();
+		cv::flip(im2_, im2_, 0);
+	}
+}
+
+void ftl::gui::Camera::update(ftl::rgbd::FrameSet &fs) {
+	UNIQUE_LOCK(mutex_, lk);
+
+	ftl::rgbd::Frame *frame = nullptr;
+
+	if (fid_ == 255) {
+		name_ = "Virtual Camera";
+		// Do a draw if not active. If active the draw function will be called
+		// directly.
+		//if (screen_->activeCamera() != this) {
+			_draw(fs);
+		//}
+	} else {
+		if (fid_ >= fs.frames.size()) return;
+		frame = &fs.frames[fid_];
+		_downloadFrames(frame);
+		auto n = frame->get<std::string>("name");
+		if (n) {
+			name_ = *n;
+		} else {
+			name_ = "No name";
+		}
+	}
 }
 
 void ftl::gui::Camera::setPose(const Eigen::Matrix4d &p) {
@@ -211,7 +325,8 @@ void ftl::gui::Camera::setPose(const Eigen::Matrix4d &p) {
 }
 
 void ftl::gui::Camera::mouseMovement(int rx, int ry, int button) {
-	if (!src_->hasCapabilities(ftl::rgbd::kCapMovable)) return;
+	//if (!src_->hasCapabilities(ftl::rgbd::kCapMovable)) return;
+	if (fid_ < 255) return;
 	if (button == 1) {
 		float rrx = ((float)ry * 0.2f * delta_);
 		//orientation_[2] += std::cos(orientation_[1])*((float)rel[1] * 0.2f * delta_);
@@ -225,7 +340,8 @@ void ftl::gui::Camera::mouseMovement(int rx, int ry, int button) {
 }
 
 void ftl::gui::Camera::keyMovement(int key, int modifiers) {
-	if (!src_->hasCapabilities(ftl::rgbd::kCapMovable)) return;
+	//if (!src_->hasCapabilities(ftl::rgbd::kCapMovable)) return;
+	if (fid_ < 255) return;
 	if (key == 263 || key == 262) {
 		float mag = (modifiers & 0x1) ? 0.01f : 0.1f;
 		float scalar = (key == 263) ? -mag : mag;
@@ -254,28 +370,39 @@ void ftl::gui::Camera::showSettings() {
 
 #ifdef HAVE_OPENVR
 bool ftl::gui::Camera::setVR(bool on) {
+	
 	if (on == vr_mode_) {
 		LOG(WARNING) << "VR mode already enabled";
 		return on;
 	}
+	vr_mode_ = on;
 
 	if (on) {
 		setChannel(Channel::Right);
-		src_->set("baseline", baseline_);
+		//src_->set("baseline", baseline_);
+		state_.getLeft().baseline = baseline_;
 
 		Eigen::Matrix3d intrinsic;
 		
 		intrinsic = getCameraMatrix(screen_->getVR(), vr::Eye_Left);
 		CHECK(intrinsic(0, 2) < 0 && intrinsic(1, 2) < 0);
-		src_->set("focal", intrinsic(0, 0));
-		src_->set("centre_x", intrinsic(0, 2));
-		src_->set("centre_y", intrinsic(1, 2));
+		//src_->set("focal", intrinsic(0, 0));
+		//src_->set("centre_x", intrinsic(0, 2));
+		//src_->set("centre_y", intrinsic(1, 2));
+		state_.getLeft().fx = intrinsic(0,0);
+		state_.getLeft().fy = intrinsic(0,0);
+		state_.getLeft().cx = intrinsic(0,2);
+		state_.getLeft().cy = intrinsic(1,2);
 		
 		intrinsic = getCameraMatrix(screen_->getVR(), vr::Eye_Right);
 		CHECK(intrinsic(0, 2) < 0 && intrinsic(1, 2) < 0);
-		src_->set("focal_right", intrinsic(0, 0));
-		src_->set("centre_x_right", intrinsic(0, 2));
-		src_->set("centre_y_right", intrinsic(1, 2));
+		//src_->set("focal_right", intrinsic(0, 0));
+		//src_->set("centre_x_right", intrinsic(0, 2));
+		//src_->set("centre_y_right", intrinsic(1, 2));
+		state_.getRight().fx = intrinsic(0,0);
+		state_.getRight().fy = intrinsic(0,0);
+		state_.getRight().cx = intrinsic(0,2);
+		state_.getRight().cy = intrinsic(1,2);
 
 		vr_mode_ = true;
 	}
@@ -284,7 +411,7 @@ bool ftl::gui::Camera::setVR(bool on) {
 		setChannel(Channel::Left); // reset to left channel
 		// todo restore camera params
 	}
-	
+
 	return vr_mode_;
 }
 #endif
@@ -298,7 +425,7 @@ void ftl::gui::Camera::setChannel(Channel c) {
 #endif
 
 	channel_ = c;
-	switch (c) {
+	/*switch (c) {
 	case Channel::Energy:
 	case Channel::Density:
 	case Channel::Flow:
@@ -319,7 +446,8 @@ void ftl::gui::Camera::setChannel(Channel c) {
 		break;
 	
 	default: src_->setChannel(Channel::None);
-	}
+	}*/
+	// FIXME: Somehow send channel request...
 }
 
 static void visualizeDepthMap(	const cv::Mat &depth, cv::Mat &out,
@@ -385,9 +513,10 @@ cv::Mat ftl::gui::Camera::visualizeActiveChannel() {
 
 bool ftl::gui::Camera::thumbnail(cv::Mat &thumb) {
 	UNIQUE_LOCK(mutex_, lk);
-	src_->grab(1,9);
-	if (im1_.empty()) return false;
-	cv::resize(im1_, thumb, cv::Size(320,180));
+	/*src_->grab(1,9);*/
+	cv::Mat sel = (channel_ != Channel::None && channel_ != Channel::Colour && !im2_.empty()) ? visualizeActiveChannel() : im1_;
+	if (sel.empty()) return false;
+	cv::resize(sel, thumb, cv::Size(320,180));
 	cv::flip(thumb, thumb, 0);
 	return true;
 }
@@ -397,7 +526,8 @@ const GLTexture &ftl::gui::Camera::captureFrame() {
 	delta_ = now - ftime_;
 	ftime_ = now;
 
-	if (src_ && src_->isReady()) {
+	//if (src_ && src_->isReady()) {
+	if (width_ > 0 && height_ > 0) {
 		UNIQUE_LOCK(mutex_, lk);
 
 		if (screen_->isVR()) {
@@ -417,7 +547,8 @@ const GLTexture &ftl::gui::Camera::captureFrame() {
 				
 				if (baseline_in != baseline_) {
 					baseline_ = baseline_in;
-					src_->set("baseline", baseline_);
+					//src_->set("baseline", baseline_);
+					state_.getLeft().baseline = baseline_;
 				}
 				Eigen::Matrix4d pose = ConvertSteamVRMatrixToMatrix4(rTrackedDevicePose_[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking);
 				Eigen::Vector3d ea = pose.block<3, 3>(0, 0).eulerAngles(0, 1, 2);
@@ -448,9 +579,9 @@ const GLTexture &ftl::gui::Camera::captureFrame() {
 		Eigen::Affine3d t(trans);
 		Eigen::Matrix4d viewPose = t.matrix() * rotmat_;
 
-		if (src_->hasCapabilities(ftl::rgbd::kCapMovable)) src_->setPose(viewPose);
+		if (isVirtual()) state_.setPose(viewPose);
 	
-		src_->grab();
+		//src_->grab();
 
 		// When switching from right to depth, client may still receive
 		// right images from previous batch (depth.channels() == 1 check)
@@ -484,7 +615,7 @@ const GLTexture &ftl::gui::Camera::captureFrame() {
 				break;
 			
 			case Channel::Depth:
-				if (im2_.rows == 0) { break; }
+				if (im2_.rows == 0 || im2_.type() != CV_32F) { break; }
 				visualizeDepthMap(im2_, tmp, 7.0);
 				if (screen_->root()->value("showEdgesInDepth", false)) drawEdges(im1_, tmp);
 				texture2_.update(tmp);
@@ -533,6 +664,7 @@ void ftl::gui::Camera::snapshot(const std::string &filename) {
 	UNIQUE_LOCK(mutex_, lk);
 	cv::Mat blended;
 	cv::Mat visualized = visualizeActiveChannel();
+
 	if (!visualized.empty()) {
 		double alpha = screen_->root()->value("blending", 0.5);
 		cv::addWeighted(im1_, alpha, visualized, 1.0-alpha, 0, blended);
@@ -541,23 +673,26 @@ void ftl::gui::Camera::snapshot(const std::string &filename) {
 	}
 	cv::Mat flipped;
 	cv::flip(blended, flipped, 0);
+	cv::cvtColor(flipped, flipped, cv::COLOR_BGRA2BGR);
 	cv::imwrite(filename, flipped);
 }
 
 void ftl::gui::Camera::startVideoRecording(const std::string &filename) {
-	fileout_->open(filename);
+	if (!record_stream_) {
+		record_stream_ = ftl::create<ftl::stream::File>(screen_->root(), "video2d");
+		record_stream_->setMode(ftl::stream::File::Mode::Write);
+		record_sender_ = ftl::create<ftl::stream::Sender>(screen_->root(), "videoEncode");
+		record_sender_->setStream(record_stream_);
+	}
 
-	writer_->begin();
-	src_->addRawCallback(recorder_);
+	if (record_stream_->active()) return;
 
-	src_->inject(Channel::Calibration, src_->parameters(), Channel::Left, src_->getCapabilities());
-	src_->inject(src_->getPose());
+	record_stream_->set("filename", filename);
+	record_stream_->begin();
 }
 
 void ftl::gui::Camera::stopVideoRecording() {
-	src_->removeRawCallback(recorder_);
-	writer_->end();
-	fileout_->close();
+	if (record_stream_ && record_stream_->active()) record_stream_->end();
 }
 
 nlohmann::json ftl::gui::Camera::getMetaData() {
diff --git a/applications/gui/src/camera.hpp b/applications/gui/src/camera.hpp
index 90914cf599942107f8d7bbba8b8f5a9505f815e9..9146d3cce1f7384c503f6cda69390fdd40570001 100644
--- a/applications/gui/src/camera.hpp
+++ b/applications/gui/src/camera.hpp
@@ -1,10 +1,14 @@
 #ifndef _FTL_GUI_CAMERA_HPP_
 #define _FTL_GUI_CAMERA_HPP_
 
-#include <ftl/rgbd/source.hpp>
+#include <ftl/rgbd/frameset.hpp>
+#include <ftl/render/tri_render.hpp>
 #include <ftl/codecs/writer.hpp>
 #include "gltexture.hpp"
 
+#include <ftl/streams/filestream.hpp>
+#include <ftl/streams/sender.hpp>
+
 #include <string>
 
 #ifdef HAVE_OPENVR
@@ -21,15 +25,13 @@ class PoseWindow;
 
 class Camera {
 	public:
-	Camera(ftl::gui::Screen *screen, ftl::rgbd::Source *src);
+	Camera(ftl::gui::Screen *screen, int fsid, int fid, ftl::codecs::Channel chan=ftl::codecs::Channel::Colour);
 	~Camera();
 
 	Camera(const Camera &)=delete;
 
-	ftl::rgbd::Source *source();
-
-	int width() { return (src_) ? src_->parameters().width : 0; }
-	int height() { return (src_) ? src_->parameters().height : 0; }
+	int width() const { return width_; }
+	int height() const { return height_; }
 
 	void setPose(const Eigen::Matrix4d &p);
 
@@ -44,7 +46,20 @@ class Camera {
 	
 	void togglePause();
 	void isPaused();
-	const ftl::codecs::Channels &availableChannels() { return channels_; }
+	inline bool isVirtual() const { return fid_ == 255; }
+	const ftl::codecs::Channels<0> &availableChannels() { return channels_; }
+
+	/**
+	 * Main function to obtain latest frames.
+	 */
+	void update(ftl::rgbd::FrameSet &fs);
+
+	/**
+	 * Update the available channels.
+	 */
+	void update(const ftl::codecs::Channels<0> &c) { channels_ = c; }
+
+	void draw(ftl::rgbd::FrameSet &fs);
 
 	const GLTexture &captureFrame();
 	const GLTexture &getLeft() const { return texture1_; }
@@ -60,6 +75,8 @@ class Camera {
 
 	nlohmann::json getMetaData();
 
+	const std::string &name() const { return name_; }
+
 	StatisticsImage *stats_ = nullptr;
 
 
@@ -74,7 +91,13 @@ class Camera {
 	cv::Mat visualizeActiveChannel();
 
 	Screen *screen_;
-	ftl::rgbd::Source *src_;
+	int fsid_;
+	int fid_;
+	// TODO: Renderer
+
+	int width_;
+	int height_;
+
 	GLTexture thumb_;
 	GLTexture texture1_; // first channel (always left at the moment)
 	GLTexture texture2_; // second channel ("right")
@@ -91,13 +114,23 @@ class Camera {
 	bool sdepth_;
 	bool pause_;
 	ftl::codecs::Channel channel_;
-	ftl::codecs::Channels channels_;
+	ftl::codecs::Channels<0> channels_;
 	cv::Mat im1_; // first channel (left)
 	cv::Mat im2_; // second channel ("right")
+
+	// FIXME: Recording will now be broken?
 	bool recording_;
-	std::ofstream *fileout_;
-	ftl::codecs::Writer *writer_;
-	ftl::rgbd::RawCallback recorder_;
+	//std::ofstream *fileout_;
+	//ftl::codecs::Writer *writer_;
+	//ftl::rgbd::RawCallback recorder_;
+
+	ftl::render::Triangular *renderer_;
+	ftl::rgbd::Frame frame_;
+	ftl::rgbd::FrameState state_;
+	ftl::stream::File *record_stream_;
+	ftl::stream::Sender *record_sender_;
+
+	std::string name_;
 
 	MUTEX mutex_;
 
@@ -106,6 +139,9 @@ class Camera {
 	bool vr_mode_;
 	float baseline_;
 	#endif
+
+	void _downloadFrames(ftl::rgbd::Frame *frame);
+	void _draw(ftl::rgbd::FrameSet &fs);
 };
 
 }
diff --git a/applications/gui/src/main.cpp b/applications/gui/src/main.cpp
index 99af829eb646f20f1e0f680837e8855739fa6be1..cd52e88ba5510e3cdb8f4fca67261cc51558c42a 100644
--- a/applications/gui/src/main.cpp
+++ b/applications/gui/src/main.cpp
@@ -31,6 +31,8 @@ int main(int argc, char **argv) {
 		std::cout << " -- " << a << std::endl;
 	}*/
 
+	ftl::timer::start();
+
 	try {
 		nanogui::init();
 
diff --git a/applications/gui/src/media_panel.cpp b/applications/gui/src/media_panel.cpp
index c2fa4285ebd433a8ac67a09eb0475e80f860100b..d7e012c5204cbab76e13bde9eed5925a46fa94ff 100644
--- a/applications/gui/src/media_panel.cpp
+++ b/applications/gui/src/media_panel.cpp
@@ -14,11 +14,12 @@
 using ftl::gui::MediaPanel;
 using ftl::codecs::Channel;
 
-MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceWindow) : nanogui::Window(screen, ""), screen_(screen) {
+MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceWindow) : nanogui::Window(screen, ""), screen_(screen), sourceWindow_(sourceWindow) {
 	using namespace nanogui;
 
 	paused_ = false;
 	disable_switch_channels_ = false;
+	record_mode_ = RecordMode::None;
 
 	setLayout(new BoxLayout(Orientation::Horizontal,
 									Alignment::Middle, 5, 10));
@@ -34,9 +35,6 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceW
 		if (cam) cam->showPoseWindow();
 	});
 
-	virtualCameraRecording_ = std::optional<ftl::gui::Camera*>();
-	sceneRecording_ = std::optional<ftl::Configurable*>();
-
 	recordbutton_ = new PopupButton(this, "", ENTYPO_ICON_CONTROLLER_RECORD);
 	recordbutton_->setTooltip("Record");
 	recordbutton_->setSide(Popup::Side::Right);
@@ -47,36 +45,23 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceW
 	recordpopup->setAnchorHeight(150);
 	auto itembutton = new Button(recordpopup, "2D snapshot (.png)");
 	itembutton->setCallback([this]() {
-		char timestamp[18];
-		std::time_t t=std::time(NULL);
-		std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
-		screen_->activeCamera()->snapshot(std::string(timestamp) + ".png");
+		_startRecording(RecordMode::Snapshot2D);
 		recordbutton_->setPushed(false);
 	});
 	itembutton = new Button(recordpopup, "Virtual camera recording (.ftl)");
 	itembutton->setCallback([this]() {
-		char timestamp[18];
-		std::time_t t=std::time(NULL);
-		std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
-		auto filename = std::string(timestamp) + ".ftl";
-		startRecording2D(screen_->activeCamera(), filename);
+		_startRecording(RecordMode::Video2D);
 		recordbutton_->setTextColor(nanogui::Color(1.0f,0.1f,0.1f,1.0f));
 		recordbutton_->setPushed(false);
 	});
 	itembutton = new Button(recordpopup, "3D scene snapshot (.ftl)");
 	itembutton->setCallback([this]() {
-		char timestamp[18];
-		std::time_t t=std::time(NULL);
-		std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
-		snapshot3D(screen_->activeCamera(), std::string(timestamp) + ".ftl");
+		_startRecording(RecordMode::Snapshot3D);
 		recordbutton_->setPushed(false);
 	});
 	itembutton = new Button(recordpopup, "3D scene recording (.ftl)");
 	itembutton->setCallback([this]() {
-		char timestamp[18];
-		std::time_t t=std::time(NULL);
-		std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
-		startRecording3D(screen_->activeCamera(), std::string(timestamp) + ".ftl");
+		_startRecording(RecordMode::Video3D);
 		recordbutton_->setTextColor(nanogui::Color(1.0f,0.1f,0.1f,1.0f));
 		recordbutton_->setPushed(false);
 	});
@@ -89,19 +74,10 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceW
 	});
 
 	recordbutton_->setCallback([this](){
-		if (virtualCameraRecording_) {
-			virtualCameraRecording_.value()->stopVideoRecording();
+		if (record_mode_ != RecordMode::None) {
+			_stopRecording();
 			recordbutton_->setTextColor(nanogui::Color(1.0f,1.0f,1.0f,1.0f));
-
-			// Prevents the popup from being opened, though it is shown while the button
-			// is being pressed.
 			recordbutton_->setPushed(false);
-			virtualCameraRecording_ = std::nullopt;
-		} else if (sceneRecording_) {
-			sceneRecording_.value()->set("record", false);
-			recordbutton_->setTextColor(nanogui::Color(1.0f,1.0f,1.0f,1.0f));
-			recordbutton_->setPushed(false);
-			sceneRecording_ = std::nullopt;
 		}
 	});
 
@@ -121,39 +97,10 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceW
 			button->setIcon(ENTYPO_ICON_CONTROLLER_PAUS);
 		}
 	});
-
-	//button = new Button(this, "", ENTYPO_ICON_CONTROLLER_RECORD);
-
-	/* Doesn't work at the moment
- #ifdef HAVE_LIBARCHIVE
-	auto button_snapshot = new Button(this, "", ENTYPO_ICON_IMAGES);
-	button_snapshot->setTooltip("Screen capture");
-	button_snapshot->setCallback([this] {
-	ftl::gui::Camera *cam = screen_->activeCamera();
-	if (!cam) return;
-
-	try {
-		char timestamp[18];
-			std::time_t t=std::time(NULL);
-			std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
-			auto writer = ftl::rgbd::SnapshotWriter(std::string(timestamp) + ".tar.gz");
-			cv::Mat rgb, depth;
-			cam->source()->getFrames(rgb, depth);
-			writer.addSource(	cam->source()->getURI(),
-								cam->source()->parameters(),
-								cam->source()->getPose());
-			writer.addRGBD(0, rgb, depth);
-		}
-		catch(std::runtime_error) {
-			LOG(ERROR) << "Snapshot failed (file error)";
-		}
-	});
-#endif
-
 	
 	// not very useful (l/r)
 
-	auto button_dual = new Button(this, "", ENTYPO_ICON_MAP);
+	/*auto button_dual = new Button(this, "", ENTYPO_ICON_MAP);
 	button_dual->setCallback([this]() {
 		screen_->setDualView(!screen_->getDualView());
 	});
@@ -188,33 +135,20 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceW
 	popup->setTheme(screen->toolbuttheme);
 	popup->setAnchorHeight(150);
 
-	button = new Button(popup, "Left");
-	button->setFlags(Button::RadioButton);
-	button->setPushed(true);
-	button->setCallback([this]() {
-		ftl::gui::Camera *cam = screen_->activeCamera();
-		if (cam) {
-			cam->setChannel(Channel::Left);
-		}
-	});
-
-	right_button_ = new Button(popup, "Right");
-	right_button_->setFlags(Button::RadioButton);
-	right_button_->setCallback([this]() {
-		ftl::gui::Camera *cam = screen_->activeCamera();
-		if (cam) {
-			cam->setChannel(Channel::Right);
-		}
-	});
-
-	depth_button_ = new Button(popup, "Depth");
-	depth_button_->setFlags(Button::RadioButton);
-	depth_button_->setCallback([this]() {
-		ftl::gui::Camera *cam = screen_->activeCamera();
-		if (cam) {
-			cam->setChannel(Channel::Depth);
-		}
-	});
+	for (int i=0; i<=2; ++i) {
+		ftl::codecs::Channel c = static_cast<ftl::codecs::Channel>(i);
+		button = new Button(popup, ftl::codecs::name(c));
+		button->setFlags(Button::RadioButton);
+		//button->setPushed(true);
+		button->setVisible(false);
+		button->setCallback([this,c]() {
+			ftl::gui::Camera *cam = screen_->activeCamera();
+			if (cam) {
+				cam->setChannel(c);
+			}
+		});
+		channel_buttons_[i] = button;
+	}
 
 	auto *popbutton = new PopupButton(popup, "More");
 	popbutton->setSide(Popup::Side::Right);
@@ -224,110 +158,75 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceW
 	popup->setTheme(screen->toolbuttheme);
 	popup->setAnchorHeight(150);
 
-	button = new Button(popup, "Deviation");
-	button->setFlags(Button::RadioButton);
-	button->setCallback([this]() {
-		ftl::gui::Camera *cam = screen_->activeCamera();
-		if (cam) {
-			cam->setChannel(Channel::Deviation);
-		}
-	});
-
-	button = new Button(popup, "Normals");
-	button->setFlags(Button::RadioButton);
-	button->setCallback([this]() {
-		ftl::gui::Camera *cam = screen_->activeCamera();
-		if (cam) {
-			cam->setChannel(Channel::ColourNormals);
-		}
-	});
-
-	button = new Button(popup, "Flow");
-	button->setFlags(Button::RadioButton);
-	button->setCallback([this]() {
-		ftl::gui::Camera *cam = screen_->activeCamera();
-		if (cam) {
-			cam->setChannel(Channel::Flow);
-		}
-	});
+	for (int i=3; i<32; ++i) {
+		ftl::codecs::Channel c = static_cast<ftl::codecs::Channel>(i);
+		button = new Button(popup, ftl::codecs::name(c));
+		button->setFlags(Button::RadioButton);
+		//button->setPushed(true);
+		button->setVisible(false);
+		button->setCallback([this,c]() {
+			ftl::gui::Camera *cam = screen_->activeCamera();
+			if (cam) {
+				cam->setChannel(c);
+			}
+		});
+		channel_buttons_[i] = button;
+	}
+}
 
-	button = new Button(popup, "Confidence");
-	button->setFlags(Button::RadioButton);
-	button->setCallback([this]() {
-		ftl::gui::Camera *cam = screen_->activeCamera();
-		if (cam) {
-			cam->setChannel(Channel::Confidence);
-		}
-	});
+MediaPanel::~MediaPanel() {
 
-	button = new Button(popup, "Energy");
-	button->setFlags(Button::RadioButton);
-	button->setCallback([this]() {
-		ftl::gui::Camera *cam = screen_->activeCamera();
-		if (cam) {
-			cam->setChannel(Channel::Energy);
-		}
-	});
+}
 
-	button = new Button(popup, "Density");
-	button->setFlags(Button::RadioButton);
-	button->setCallback([this]() {
-		ftl::gui::Camera *cam = screen_->activeCamera();
-		if (cam) {
-			cam->setChannel(Channel::Density);
-		}
-	});
+void MediaPanel::_startRecording(MediaPanel::RecordMode mode) {
+	char timestamp[18];
+	std::time_t t=std::time(NULL);
+	std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
+
+	std::string filename(timestamp);
+	switch(mode) {
+	case RecordMode::Snapshot2D		: filename += ".png"; break;
+	case RecordMode::Snapshot3D		:
+	case RecordMode::Video3D		: filename += ".ftl"; break;
+	case RecordMode::Video2D		: filename += ".ftl"; break;
+	}
 
+	if (mode == RecordMode::Video3D) {
+		record_mode_ = mode;
+		sourceWindow_->recordVideo(filename);
+	} else if (mode == RecordMode::Snapshot2D) {
+		screen_->activeCamera()->snapshot(filename);
+	} else if (mode == RecordMode::Video2D) {
+		record_mode_ = mode;
+		screen_->activeCamera()->startVideoRecording(filename);
+	}
 }
 
-MediaPanel::~MediaPanel() {
-
+void MediaPanel::_stopRecording() {
+	if (record_mode_ == RecordMode::Video3D) {
+		sourceWindow_->stopRecordingVideo();
+	} else if (record_mode_ == RecordMode::Video2D) {
+		screen_->activeCamera()->stopVideoRecording();
+	}
+	record_mode_ = RecordMode::None;
 }
 
 // Update button enabled status
 void MediaPanel::cameraChanged() {
 	ftl::gui::Camera *cam = screen_->activeCamera();
 	if (cam) {
-		if (cam->source()->hasCapabilities(ftl::rgbd::kCapStereo)) {
-			right_button_->setEnabled(true);
-		} else {
-			right_button_->setEnabled(false);
-		}
-	}
-}
-
-void MediaPanel::startRecording2D(ftl::gui::Camera *camera, const std::string &filename) {
-	camera->startVideoRecording(filename);
-	virtualCameraRecording_ = std::optional<ftl::gui::Camera*>(camera);
-	recordbutton_->setTextColor(nanogui::Color(1.0f,0.1f,0.1f,1.0f));
-}
-
-void MediaPanel::snapshot3D(ftl::gui::Camera *camera, const std::string &filename) {
-	auto tag = camera->source()->get<std::string>("uri");
-	if (tag) {
-		auto tagvalue = tag.value();
-		auto configurables = ftl::config::findByTag(tagvalue);
-		if (configurables.size() > 0) {
-			ftl::Configurable *configurable = ftl::config::find(configurables[0]->getID() + "/controls");
-			if (configurable) {
-				configurable->set("3D-snapshot", filename);
+		auto channels = cam->availableChannels();
+		for (int i=0; i<32; ++i) {
+			if (channels.has(static_cast<ftl::codecs::Channel>(i))) {
+				channel_buttons_[i]->setVisible(true);
+			} else {
+				channel_buttons_[i]->setVisible(false);
 			}
-		}
-	}
-}
 
-void MediaPanel::startRecording3D(ftl::gui::Camera *camera, const std::string &filename) {
-	auto tag = camera->source()->get<std::string>("uri");
-	if (tag) {
-		auto tagvalue = tag.value();
-		auto configurables = ftl::config::findByTag(tagvalue);
-		if (configurables.size() > 0) {
-			ftl::Configurable *configurable = ftl::config::find(configurables[0]->getID() + "/controls");
-			if (configurable) {
-				configurable->set("record-name", filename);
-				configurable->set("record", true);
-				sceneRecording_ = std::optional<ftl::Configurable*>(configurable);
-				recordbutton_->setTextColor(nanogui::Color(1.0f,0.1f,0.1f,1.0f));
+			if (cam->getChannel() == static_cast<ftl::codecs::Channel>(i)) {
+				channel_buttons_[i]->setPushed(true);
+			} else {
+				channel_buttons_[i]->setPushed(false);
 			}
 		}
 	}
diff --git a/applications/gui/src/media_panel.hpp b/applications/gui/src/media_panel.hpp
index 211b84c6e0beecdcfd5fd51cb3f413e628d31d87..4cf69b5ba204d268ad9143b582d5a71a6d407c67 100644
--- a/applications/gui/src/media_panel.hpp
+++ b/applications/gui/src/media_panel.hpp
@@ -7,6 +7,8 @@
 
 #include "src_window.hpp"
 
+#include <array>
+
 namespace ftl {
 
 namespace rgbd {
@@ -24,25 +26,39 @@ class MediaPanel : public nanogui::Window {
 
 	void cameraChanged();
 
-	void startRecording2D(ftl::gui::Camera *camera, const std::string &filename);
+	//void startRecording2D(ftl::gui::Camera *camera, const std::string &filename);
 
-	void snapshot3D(ftl::gui::Camera *camera, const std::string &filename);
+	//void snapshot3D(ftl::gui::Camera *camera, const std::string &filename);
 
-	void startRecording3D(ftl::gui::Camera *camera, const std::string &filename);
+	//void startRecording3D(ftl::gui::Camera *camera, const std::string &filename);
 
 	void recordWindowClosed();
 
 	private:
 	ftl::gui::Screen *screen_;
+	ftl::gui::SourceWindow *sourceWindow_;
 
 	bool paused_;
 	bool disable_switch_channels_;
 
 	ftl::rgbd::SnapshotStreamWriter *writer_;
 	nanogui::PopupButton *button_channels_;
-	nanogui::Button *right_button_;
-	nanogui::Button *depth_button_;
+	//nanogui::Button *right_button_;
+	//nanogui::Button *depth_button_;
 	nanogui::PopupButton *recordbutton_;
+	std::array<nanogui::Button*,32> channel_buttons_={};
+
+	enum class RecordMode {
+		None,
+		Snapshot2D,
+		Snapshot3D,
+		Video2D,
+		Video3D
+	};
+	RecordMode record_mode_;
+
+	void _startRecording(RecordMode mode);
+	void _stopRecording();
 
 	/**
 	 * These members indicate which type of recording is active, if any.
@@ -50,8 +66,8 @@ class MediaPanel : public nanogui::Window {
 	 * to end the recording. Only one of these members should have a value
 	 * at any given time.
 	 */
-	std::optional<ftl::gui::Camera*> virtualCameraRecording_;
-	std::optional<ftl::Configurable*> sceneRecording_;
+	//std::optional<ftl::gui::Camera*> virtualCameraRecording_;
+	//std::optional<ftl::Configurable*> sceneRecording_;
 };
 
 }
diff --git a/applications/gui/src/record_window.cpp b/applications/gui/src/record_window.cpp
index 87fbb9dc0e50562abe10b932b05f728f4e24208e..f74fd61aa65b392fc07e361a94dbead1bd7cd183 100644
--- a/applications/gui/src/record_window.cpp
+++ b/applications/gui/src/record_window.cpp
@@ -33,11 +33,14 @@ RecordWindow::RecordWindow(nanogui::Widget *parent, ftl::gui::Screen *screen, co
     auto streamNames = std::vector<std::string>();
     streamNames.reserve(streams.size());
     std::optional<int> ix;
+	int i=1;
     for (const auto s : streams) {
         if (s == screen->activeCamera()) {
             ix = std::optional<int>(streamNames.size());
         }
-        streamNames.push_back(s->source()->getURI());
+        // FIXME: Find alternative to source URI
+        //streamNames.push_back(s->source()->getURI());
+		streamNames.push_back(std::string("Stream")+std::to_string(i++));
     }
     auto streamSelect = new ComboBox(this, streamNames);
     // TODO: The function availableChannels() only finds those channels that
@@ -103,11 +106,11 @@ RecordWindow::RecordWindow(nanogui::Widget *parent, ftl::gui::Screen *screen, co
         } else if (tab == recording2D) {
             stream->setChannel(channels_[recordingChannel->selectedIndex()]);
             screen->setActiveCamera(stream);
-            media_panel->startRecording2D(stream, name);
+            //media_panel->startRecording2D(stream, name);
         } else if (tab == snapshot3D) {
-            media_panel->snapshot3D(stream, name);
+            //media_panel->snapshot3D(stream, name);
         } else if (tab == recording3D) {
-            media_panel->startRecording3D(stream, name);
+            //media_panel->startRecording3D(stream, name);
         }
         dispose();
         media_panel->recordWindowClosed();
diff --git a/applications/gui/src/scene.hpp b/applications/gui/src/scene.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2e2b4b89fee844ea35905a51df6ee3a424f9ef4c
--- /dev/null
+++ b/applications/gui/src/scene.hpp
@@ -0,0 +1,25 @@
+#ifndef _FTL_GUI_SCENE_HPP_
+#define _FTL_GUI_SCENE_HPP_
+
+#include <ftl/streams/receiver.hpp>
+
+namespace ftl {
+namespace gui {
+
+class Camera;
+
+class Scene {
+	public:
+	explicit Scene(ftl::stream::Receiver *);
+	~Scene();
+
+	inline const std::vector<ftl::gui::Camera*> cameras() const { return cameras_; };
+
+	private:
+	std::vector<ftl::gui::Camera*> cameras_;
+};
+
+}
+}
+
+#endif  // _FTL_GUI_SCENE_HPP_
diff --git a/applications/gui/src/screen.cpp b/applications/gui/src/screen.cpp
index aa2d5e36e7c9f4dfa29cd6206efac2110f4e7d36..787d209919ef339eb71e46bda70a371d461eec8a 100644
--- a/applications/gui/src/screen.cpp
+++ b/applications/gui/src/screen.cpp
@@ -363,7 +363,8 @@ void ftl::gui::Screen::setActiveCamera(ftl::gui::Camera *cam) {
 	camera_ = cam;
 
 	if (cam) {
-		status_ = cam->source()->getURI();
+		// FIXME: Alternative to source URI
+		status_ = cam->name();
 		mwindow_->setVisible(true);
 		mwindow_->cameraChanged();
 		swindow_->setVisible(false);
diff --git a/applications/gui/src/src_window.cpp b/applications/gui/src/src_window.cpp
index 6b930c110bd21280eb1df1395cb11149d3980fe8..bb2105ce95b4b39786d57171430c50eff8b9f65b 100644
--- a/applications/gui/src/src_window.cpp
+++ b/applications/gui/src/src_window.cpp
@@ -2,6 +2,7 @@
 
 #include "screen.hpp"
 #include "camera.hpp"
+#include "scene.hpp"
 
 #include <nanogui/imageview.h>
 #include <nanogui/textbox.h>
@@ -14,6 +15,13 @@
 #include <nanogui/layout.h>
 #include <nanogui/vscrollpanel.h>
 
+#include <ftl/streams/netstream.hpp>
+
+#include "ftl/operators/colours.hpp"
+#include "ftl/operators/segmentation.hpp"
+#include "ftl/operators/mask.hpp"
+#include "ftl/operators/antialiasing.hpp"
+
 #ifdef HAVE_LIBARCHIVE
 #include "ftl/rgbd/snapshot.hpp"
 #endif
@@ -22,7 +30,9 @@
 
 using ftl::gui::SourceWindow;
 using ftl::gui::Screen;
+using ftl::gui::Scene;
 using ftl::rgbd::Source;
+using ftl::codecs::Channel;
 using std::string;
 using std::vector;
 using ftl::config::json_t;
@@ -33,31 +43,12 @@ SourceWindow::SourceWindow(ftl::gui::Screen *screen)
 
 	using namespace nanogui;
 	
-	//if (!screen->root()->get<json_t>("sources")) {
-	//	screen->root()->getConfig()["sources"] = json_t::array();
-	//}
-
-	//src_ = ftl::create<Source>(ctrl->getRoot(), "source", ctrl->getNet());
-
-	//Widget *tools = new Widget(this);
-	//    tools->setLayout(new BoxLayout(Orientation::Horizontal,
-	//                                   Alignment::Middle, 0, 6));
-
 	new Label(this, "Select Camera","sans-bold",20);
 
-	//auto select = new ComboBox(this, available_);
-	//select->setCallback([this,select](int ix) {
-		//src_->set("uri", available_[ix]);
-		// TODO(Nick) Check camera exists first
-	//	screen_->setActiveCamera(cameras_[available_[ix]]);
-	//	LOG(INFO) << "Change source: " << ix;
-	//});
-
 	auto vscroll = new VScrollPanel(this);
 	ipanel_ = new Widget(vscroll);
 	ipanel_->setLayout(new GridLayout(nanogui::Orientation::Horizontal, 2,
 		nanogui::Alignment::Middle, 0, 5));
-	//ipanel_ = new ImageView(vscroll, 0);
 
 	screen->net()->onConnect([this](ftl::net::Peer *p) {
 		UNIQUE_LOCK(mutex_, lk);
@@ -65,50 +56,185 @@ SourceWindow::SourceWindow(ftl::gui::Screen *screen)
 	});
 
 	UNIQUE_LOCK(mutex_, lk);
+	stream_ = ftl::create<ftl::stream::Muxer>(screen->root(), "muxer");
+	interceptor_ = ftl::create<ftl::stream::Intercept>(screen->root(), "intercept");
+	interceptor_->setStream(stream_);
+	receiver_ = ftl::create<ftl::stream::Receiver>(screen->root(), "receiver");
+	receiver_->setStream(interceptor_);
 
-	std::vector<ftl::rgbd::Source*> srcs = ftl::createArray<ftl::rgbd::Source>(screen_->root(), "sources", screen_->net());
-	for (auto *src : srcs) {
-		available_.push_back(src->getURI());
-	}
+	// Create a recorder
+	recorder_ = ftl::create<ftl::stream::File>(screen->root(), "recorder");
+	recorder_->setMode(ftl::stream::File::Mode::Write);
+
+	interceptor_->onIntercept([this] (const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+		//LOG(INFO) << (std::string)spkt;
+		if (recorder_->active() && pkt.data.size() > 0) recorder_->post(spkt, pkt);
+	});
+
+	pre_pipeline_ = ftl::config::create<ftl::operators::Graph>(screen->root(), "pre_filters");
+	//pre_pipeline_->append<ftl::operators::ColourChannels>("colour");  // Convert BGR to BGRA
+	pre_pipeline_->append<ftl::operators::CrossSupport>("cross");
+	pre_pipeline_->append<ftl::operators::DiscontinuityMask>("discontinuity");
+	pre_pipeline_->append<ftl::operators::CullDiscontinuity>("remove_discontinuity");
+	pre_pipeline_->append<ftl::operators::VisCrossSupport>("viscross")->set("enabled", false);
+
+	cycle_ = 0;
+	receiver_->onFrameSet([this](ftl::rgbd::FrameSet &fs) {
+		// Request the channels required by current camera configuration
+		interceptor_->select(fs.id, _aggregateChannels());
+
+		const auto *cstream = interceptor_;
+		_createDefaultCameras(fs, cstream->available(fs.id).has(Channel::Depth));
+
+		//LOG(INFO) << "Channels = " << (unsigned int)cstream->available(fs.id);
+
+		// Enforce interpolated colour
+		for (int i=0; i<fs.frames.size(); ++i) {
+			fs.frames[i].createTexture<uchar4>(Channel::Colour, true);
+		}
+
+		pre_pipeline_->apply(fs, fs, 0);
+
+		int i=0;
+		for (auto cam : cameras_) {
+			// Only update the camera periodically unless the active camera
+			if (screen_->activeCamera() == cam.second.camera ||
+				(screen_->activeCamera() == nullptr && cycle_ % cameras_.size() == i++))  cam.second.camera->update(fs);
+
+			cam.second.camera->update(cstream->available(fs.id));
+		}
+		++cycle_;
+
+		return true;
+	});
 
 	_updateCameras(screen_->control()->getNet()->findAll<string>("list_streams"));
+
+	// Also check for a file on command line.
+	// Check paths for FTL files to load.
+	auto paths = (*screen->root()->get<nlohmann::json>("paths"));
+
+	for (auto &x : paths.items()) {
+		std::string path = x.value().get<std::string>();
+		auto eix = path.find_last_of('.');
+		auto ext = path.substr(eix+1);
+
+		// Command line path is ftl file
+		if (ext == "ftl") {
+			LOG(INFO) << "Found FTL file: " << path;
+			auto *fstream = ftl::create<ftl::stream::File>(screen->root(), "ftlfile");
+			fstream->set("filename", path);
+			stream_->add(fstream);
+		}
+	}
+
+	stream_->begin();
+}
+
+void SourceWindow::recordVideo(const std::string &filename) {
+	if (!recorder_->active()) {
+		recorder_->set("filename", filename);
+		recorder_->begin();
+		LOG(INFO) << "Recording started: " << filename;
+
+		// TODO: Inject pose and calibrations
+	}
+}
+
+void SourceWindow::stopRecordingVideo() {
+	if (recorder_->active()) {
+		recorder_->end();
+		LOG(INFO) << "Recording stopped.";
+	}
+}
+
+ftl::codecs::Channels<0> SourceWindow::_aggregateChannels() {
+	ftl::codecs::Channels<0> cs = ftl::codecs::Channels<0>(Channel::Colour);
+	for (auto cam : cameras_) {
+		if (cam.second.camera->isVirtual()) {
+			cs += Channel::Depth;
+		} else {
+			if (cam.second.camera->getChannel() != Channel::None) {
+				cs += cam.second.camera->getChannel();
+			}
+		}
+	}
+	return cs;
+}
+
+void SourceWindow::_createDefaultCameras(ftl::rgbd::FrameSet &fs, bool makevirtual) {
+	for (int i=0; i<fs.frames.size(); ++i) {
+		int id = i;  // TODO: Include frameset id
+		if (cameras_.find(id) == cameras_.end()) {
+			auto *cam = new ftl::gui::Camera(screen_, 0, i);
+			cameras_[id] = {
+				cam,
+				nullptr
+			};
+		}
+	}
+
+	if (makevirtual && cameras_.find(-1) == cameras_.end()) {
+		auto *cam = new ftl::gui::Camera(screen_, 0, 255);
+		cameras_[-1] = {
+			cam,
+			nullptr
+		};
+	}
 }
 
 std::vector<ftl::gui::Camera*> SourceWindow::getCameras() {
 	auto cameras = std::vector<ftl::gui::Camera*>();
 	cameras.reserve(cameras_.size());
-	for (const auto &kv : cameras_) {
-		cameras.push_back(kv.second);
+
+	for (auto cam : cameras_) {
+		cameras.push_back(cam.second.camera);
 	}
 	return cameras;
 }
 
 void SourceWindow::_updateCameras(const vector<string> &netcams) {
+	if (netcams.size() == 0) return;
+
 	for (auto s : netcams) {
-		if (cameras_.find(s) == cameras_.end()) {
+		// FIXME: Check for already existing...
+		//if (streams_.find(s) == cameras_.end()) {
 			available_.push_back(s);
 			json_t srcjson;
 			srcjson["uri"] = s;
-			screen_->root()->getConfig()["sources"].push_back(srcjson);
-		}
+			screen_->root()->getConfig()["streams"].push_back(srcjson);
+			//screen_->root()->getConfig()["receivers"].push_back(json_t{});
+		//}
 	}
 
-	std::vector<ftl::rgbd::Source*> srcs = ftl::createArray<ftl::rgbd::Source>(screen_->root(), "sources", screen_->net());
-	for (auto *src : srcs) {
-		if (cameras_.find(src->getURI()) == cameras_.end()) {
+	std::vector<ftl::stream::Net*> strms = ftl::createArray<ftl::stream::Net>(screen_->root(), "streams", screen_->net());
+
+	for (int i=0; i<strms.size(); ++i) {
+		auto *stream = strms[i];
+		stream_->add(stream);
+
+		LOG(INFO) << "Add Stream: " << stream->value("uri", std::string("NONE"));
+
+		//Scene *scene = new Scene(receiver);
+		//scenes_.push_back(scene);
+
+		/*if (.find(src->getURI()) == cameras_.end()) {
 			LOG(INFO) << "Making camera: " << src->getURI();
 
+			// TODO: Need to have GUI wrapper for an entire stream... which
+			// manages a set of cameras.
+
 			auto *cam = new ftl::gui::Camera(screen_, src);
 			cameras_[src->getURI()] = cam;
 		} else {
 			//LOG(INFO) << "Camera already exists: " << s;
-		}
+		}*/
 	}
 
-	refresh_thumbs_ = true;
-	if (thumbs_.size() != available_.size()) {
-		thumbs_.resize(available_.size());
-	}
+	//refresh_thumbs_ = true;
+	//if (thumbs_.size() != available_.size()) {
+	//	thumbs_.resize(available_.size());
+	//}
 }
 
 SourceWindow::~SourceWindow() {
@@ -116,31 +242,36 @@ SourceWindow::~SourceWindow() {
 }
 
 void SourceWindow::draw(NVGcontext *ctx) {
-	if (refresh_thumbs_) {
+	//if (refresh_thumbs_) {
 		UNIQUE_LOCK(mutex_, lk);
 		//refresh_thumbs_ = false;
 
-		for (size_t i=0; i<thumbs_.size(); ++i) {
+		if (thumbs_.size() < cameras_.size()) thumbs_.resize(cameras_.size());
+
+		//for (size_t i=0; i<thumbs_.size(); ++i) {
+		int i = 0;
+		for (auto &camera : cameras_) {
 			cv::Mat t;
-			auto *cam = cameras_[available_[i]];
+			auto *cam = camera.second.camera;
 			if (cam) {
 				if (cam->thumbnail(t)) {
 					thumbs_[i].update(t);
-				} else {
-					refresh_thumbs_ = true;
 				}
 			}
 
-			if ((size_t)ipanel_->childCount() < i+1) {
+			if (!camera.second.thumbview) camera.second.thumbview = new ftl::gui::ThumbView(ipanel_, screen_, cam);
+
+			/*if ((size_t)ipanel_->childCount() < i+1) {
 				new ftl::gui::ThumbView(ipanel_, screen_, cam);
-			}
-			if (thumbs_[i].isValid()) dynamic_cast<nanogui::ImageView*>(ipanel_->childAt(i))->bindImage(thumbs_[i].texture());
+			}*/
+			if (thumbs_[i].isValid()) dynamic_cast<nanogui::ImageView*>(camera.second.thumbview)->bindImage(thumbs_[i].texture());
+			++i;
 		}
 
 		// TODO(Nick) remove excess image views
 
 		center();
-	}
+	//}
 
 	nanogui::Window::draw(ctx);
 }
diff --git a/applications/gui/src/src_window.hpp b/applications/gui/src/src_window.hpp
index dab3f5d4301de73d0dda2bbd32b321c3f796d160..5e8164954d090e089f99bf75514891f2c1f51221 100644
--- a/applications/gui/src/src_window.hpp
+++ b/applications/gui/src/src_window.hpp
@@ -12,14 +12,28 @@
 #include <string>
 #include "gltexture.hpp"
 
+#include <ftl/streams/stream.hpp>
+#include <ftl/streams/receiver.hpp>
+#include <ftl/streams/filestream.hpp>
+
 class VirtualCameraView;
 
 namespace ftl {
 namespace gui {
 
 class Screen;
+class Scene;
 class Camera;
+class ThumbView;
 
+/**
+ * Main class for managing all data streams and corresponding cameras. It
+ * will automatically locate all available streams and generate default cameras
+ * for each frame of each stream found. It will also add a single default
+ * virtual camera. Additional cameras can be added. This class directly
+ * receives all frameset data and then forwards it to the individual cameras
+ * for drawing/rendering.
+ */
 class SourceWindow : public nanogui::Window {
 	public:
 	explicit SourceWindow(ftl::gui::Screen *screen);
@@ -29,16 +43,34 @@ class SourceWindow : public nanogui::Window {
 
 	virtual void draw(NVGcontext *ctx);
 
+	void recordVideo(const std::string &filename);
+	void stopRecordingVideo();
+
 	private:
 	ftl::gui::Screen *screen_;
-	std::map<std::string, ftl::gui::Camera*> cameras_; 
+
+	struct CameraEntry {
+		ftl::gui::Camera *camera;
+		ftl::gui::ThumbView *thumbview;
+		//GLTexture thumb;
+	};
+
+	std::map<int, CameraEntry> cameras_;
+	ftl::stream::Muxer *stream_;
+	ftl::stream::Intercept *interceptor_;
+	ftl::stream::File *recorder_;
+	ftl::stream::Receiver *receiver_;
 	std::vector<std::string> available_;
 	std::vector<GLTexture> thumbs_;
 	bool refresh_thumbs_;
 	nanogui::Widget *ipanel_;
+	int cycle_;
+	ftl::operators::Graph *pre_pipeline_;
 	MUTEX mutex_;
 
 	void _updateCameras(const std::vector<std::string> &netcams);
+	void _createDefaultCameras(ftl::rgbd::FrameSet &fs, bool makevirtual);
+	ftl::codecs::Channels<0> _aggregateChannels();
 
 };
 
diff --git a/applications/gui/src/thumbview.cpp b/applications/gui/src/thumbview.cpp
index 2870985873ab2a87e921e4376fabf66f965348ce..6c567e516482785dbaa0d7d69aa08bef3ed1a8e4 100644
--- a/applications/gui/src/thumbview.cpp
+++ b/applications/gui/src/thumbview.cpp
@@ -29,6 +29,6 @@ void ThumbView::draw(NVGcontext *ctx) {
 	nvgScissor(ctx, mPos.x(), mPos.y(), mSize.x(), mSize.y());
 	nvgFontSize(ctx, 14);
 	nvgFontFace(ctx, "sans-bold");
-	nvgText(ctx, mPos.x() + 10, mPos.y()+mSize.y() - 10, cam_->source()->getURI().c_str(), NULL);
+	//nvgText(ctx, mPos.x() + 10, mPos.y()+mSize.y() - 10, cam_->source()->getURI().c_str(), NULL);
 	nvgResetScissor(ctx);
 }
diff --git a/applications/reconstruct/CMakeLists.txt b/applications/reconstruct/CMakeLists.txt
index 5dc0ef5dbce517fb88cdf0fb0cc9fdc7b84a778a..b039e8466ee5374a33daa29716ec1c04c5fd2619 100644
--- a/applications/reconstruct/CMakeLists.txt
+++ b/applications/reconstruct/CMakeLists.txt
@@ -15,11 +15,11 @@ set(REPSRC
 	#src/mls.cu
 	#src/depth_camera.cu
 	#src/depth_camera.cpp
-	src/ilw/ilw.cpp
-	src/ilw/ilw.cu
-	src/ilw/fill.cu
-	src/ilw/discontinuity.cu
-	src/ilw/correspondence.cu
+	#src/ilw/ilw.cpp
+	#src/ilw/ilw.cu
+	#src/ilw/fill.cu
+	#src/ilw/discontinuity.cu
+	#src/ilw/correspondence.cu
 	src/reconstruction.cpp
 )
 
@@ -37,6 +37,6 @@ set_property(TARGET ftl-reconstruct PROPERTY CUDA_SEPARABLE_COMPILATION ON)
 endif()
 
 #target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include)
-target_link_libraries(ftl-reconstruct ftlcommon ftlrgbd Threads::Threads ${OpenCV_LIBS} ftlctrl ftlnet ftlrender ftloperators)
+target_link_libraries(ftl-reconstruct ftlcommon ftlrgbd Threads::Threads ${OpenCV_LIBS} ftlctrl ftlnet ftlrender ftloperators ftlstreams)
 
 
diff --git a/applications/reconstruct/src/main.cpp b/applications/reconstruct/src/main.cpp
index 5cce7a5a236699a58e74a1044dfef35a008bf72e..8108a6b5d74a9b7effcd75df4f6651d1249102e4 100644
--- a/applications/reconstruct/src/main.cpp
+++ b/applications/reconstruct/src/main.cpp
@@ -48,6 +48,11 @@
 #include <ftl/codecs/h264.hpp>
 #include <ftl/codecs/hevc.hpp>
 
+#include <ftl/streams/filestream.hpp>
+#include <ftl/streams/receiver.hpp>
+#include <ftl/streams/sender.hpp>
+#include <ftl/streams/netstream.hpp>
+
 #include <cuda_profiler_api.h>
 
 #ifdef WIN32
@@ -64,10 +69,6 @@ using ftl::codecs::Channel;
 using json = nlohmann::json;
 using std::this_thread::sleep_for;
 using std::chrono::milliseconds;
-//using std::mutex;
-//using std::unique_lock;
-
-//using cv::Mat;
 
 using ftl::registration::loadTransformations;
 using ftl::registration::saveTransformations;
@@ -82,6 +83,57 @@ static Eigen::Affine3d create_rotation_matrix(float ax, float ay, float az) {
 	return rz * rx * ry;
 }
 
+/* Build a generator using a deprecated list of source objects. */
+static ftl::rgbd::Generator *createSourceGenerator(const std::vector<ftl::rgbd::Source*> &srcs) {
+	// Must find pose for each source...
+	if (srcs.size() > 1) {
+		std::map<std::string, Eigen::Matrix4d> transformations;
+
+		if (loadTransformations(string(FTL_LOCAL_CONFIG_ROOT) + "/registration.json", transformations)) {
+			LOG(INFO) << "Loaded camera trasformations from file";
+		}
+		else {
+			LOG(ERROR) << "Error loading camera transformations from file";
+		}
+
+		for (auto &input : srcs) {
+			string uri = input->getURI();
+
+			auto T = transformations.find(uri);
+			if (T == transformations.end()) {
+				LOG(WARNING) << "Camera pose for " + uri + " not found in transformations";
+				//LOG(WARNING) << "Using only first configured source";
+				// TODO: use target source if configured and found
+				//sources = { sources[0] };
+				//sources[0]->setPose(Eigen::Matrix4d::Identity());
+				//break;
+				input->setPose(input->getPose());
+				continue;
+			}
+			input->setPose(T->second);
+		}
+	}
+
+	auto *grp = new ftl::rgbd::Group();
+	for (auto s : srcs) {
+		s->setChannel(Channel::Depth);
+		grp->addSource(s);
+	}
+	return grp;
+}
+
+static ftl::rgbd::Generator *createFileGenerator(ftl::Configurable *root, const std::string &filename) {
+	ftl::stream::File *stream = ftl::create<ftl::stream::File>(root, "player");
+	stream->set("filename", filename);
+
+	ftl::stream::Receiver *gen = ftl::create<ftl::stream::Receiver>(root, "receiver");
+	gen->setStream(stream);
+
+	stream->begin();
+	stream->select(0, Channel::Colour + Channel::Depth);  // TODO: Choose these elsewhere
+	return gen;
+}
+
 static void run(ftl::Configurable *root) {
 	Universe *net = ftl::create<Universe>(root, "net");
 	ftl::ctrl::Master ctrl(root, net);
@@ -92,19 +144,34 @@ static void run(ftl::Configurable *root) {
 	net->start();
 	net->waitConnections();
 
-	std::vector<int> sourcecounts;
-
-	// Add sources from the configuration file as a single group.
-	auto configuration_sources = root->getConfig()["sources"];
-	size_t configuration_size = configuration_sources.size();
-	if (configuration_size > 0) {
-		sourcecounts.push_back(configuration_size);
-	}
+	vector<ftl::Reconstruction*> groups;
 
 	ftl::codecs::Channels channels;
 
+	ftl::stream::Sender *sender = ftl::create<ftl::stream::Sender>(root, "sender");
+	ftl::stream::Net *outstream = ftl::create<ftl::stream::Net>(root, "stream", net);
+	outstream->set("uri", "ftl://test.utu.fi");
+	outstream->begin();
+	sender->setStream(outstream);
+
+	std::vector<Source*> sources;
+	// Create a vector of all input RGB-Depth sources
+	if (root->getConfig()["sources"].size() > 0) {
+		sources = ftl::createArray<Source>(root, "sources", net);
+		auto *gen = createSourceGenerator(sources);
+		auto reconstr = ftl::create<ftl::Reconstruction>(root, "0", "0");
+		reconstr->setGenerator(gen);
+		reconstr->onFrameSet([sender](ftl::rgbd::FrameSet &fs) {
+			fs.id = 0;
+			sender->post(fs);
+			return true;
+		});
+		groups.push_back(reconstr);
+	}
+
 	// Check paths for FTL files to load.
 	auto paths = (*root->get<nlohmann::json>("paths"));
+	int i = groups.size();
 	for (auto &x : paths.items()) {
 		std::string path = x.value().get<std::string>();
 		auto eix = path.find_last_of('.');
@@ -127,220 +194,58 @@ static void run(ftl::Configurable *root) {
 
 			LOG(INFO) << "Found " << (max_stream+1) << " sources in " << path;
 
-			int N = root->value("N", 100);
-
-			// For each stream found, add a source object
-			int count = min(max_stream+1, N);
-			for (int i=0; i<count; ++i) {
-				root->getConfig()["sources"].push_back(nlohmann::json{{"uri",std::string("file://") + path + std::string("#") + std::to_string(i)}});
-			}
-			sourcecounts.push_back(count);
+			auto *gen = createFileGenerator(root, path);
+			auto reconstr = ftl::create<ftl::Reconstruction>(root, std::string("recon")+std::to_string(i), std::to_string(i));
+			reconstr->setGenerator(gen);
+			reconstr->onFrameSet([sender,i](ftl::rgbd::FrameSet &fs) {
+				fs.id = i;
+				sender->post(fs);
+				return true;
+			});
+			groups.push_back(reconstr);
+			++i;
 		}
 	}
 
-	if (channels.empty()) channels |= Channel::Depth;
-
-	// Create a vector of all input RGB-Depth sources
-	auto sources = ftl::createArray<Source>(root, "sources", net);
-
-	if (sources.size() == 0) {
+	if (groups.size() == 0) {
 		LOG(ERROR) << "No sources configured!";
-		return;
-	}
-
-	// Must find pose for each source...
-	if (sources.size() > 1) {
-		std::map<std::string, Eigen::Matrix4d> transformations;
-
-		if (loadTransformations(string(FTL_LOCAL_CONFIG_ROOT) + "/registration.json", transformations)) {
-			LOG(INFO) << "Loaded camera trasformations from file";
-		}
-		else {
-			LOG(ERROR) << "Error loading camera transformations from file";
-		}
-
-		for (auto &input : sources) {
-			string uri = input->getURI();
-
-			auto T = transformations.find(uri);
-			if (T == transformations.end()) {
-				LOG(WARNING) << "Camera pose for " + uri + " not found in transformations";
-				//LOG(WARNING) << "Using only first configured source";
-				// TODO: use target source if configured and found
-				//sources = { sources[0] };
-				//sources[0]->setPose(Eigen::Matrix4d::Identity());
-				//break;
-				input->setPose(input->getPose());
-				continue;
-			}
-			input->setPose(T->second);
-		}
-	}
 
-	ftl::rgbd::FrameSet fs_out;
+		auto stream_uris = net->findAll<std::string>("list_streams");
 
-	//ftl::voxhash::SceneRep *scene = ftl::create<ftl::voxhash::SceneRep>(root, "voxelhash");
-	ftl::rgbd::Streamer *stream = ftl::create<ftl::rgbd::Streamer>(root, "stream", net);
-	ftl::rgbd::VirtualSource *vs = ftl::create<ftl::rgbd::VirtualSource>(root, "virtual");
-	auto tags = root->value<std::vector<std::string>>("tags", nlohmann::json::array({}));
-	tags.push_back(root->getID()+"/virtual");
-	root->set("tags", tags);
+		if (stream_uris.size() > 0) {
+			ftl::stream::Muxer *stream = ftl::create<ftl::stream::Muxer>(root, "muxstream");
+			ftl::stream::Receiver *gen = ftl::create<ftl::stream::Receiver>(root, "receiver");
+			gen->setStream(stream);
 
-	int o = root->value("origin_pose", 0) % sources.size();
-	vs->setPose(sources[o]->getPose());
-
-	vector<ftl::Reconstruction*> groups;
-
-	size_t cumulative = 0;
-	for (auto c : sourcecounts) {
-		std::string id = std::to_string(cumulative);
-		auto reconstr = ftl::create<ftl::Reconstruction>(root, id, id);
-		for (size_t i=cumulative; i<cumulative+c; i++) {
-			if (channels.has(Channel::Depth)) {
-				sources[i]->setChannel(Channel::Depth);
-			} else if (channels.has(Channel::Right)) {
-				sources[i]->setChannel(Channel::Right);
+			int count = 0;
+			for (auto &s : stream_uris) {
+				LOG(INFO) << " --- found stream: " << s;
+				auto *nstream = ftl::create<ftl::stream::Net>(stream, std::to_string(count), net);
+				nstream->set("uri", s);
+				stream->add(nstream);
+				++count;
 			}
-			reconstr->addSource(sources[i]);
-		}
-		groups.push_back(reconstr);
-		cumulative += c;
-	}
-
-	auto *renderpipe = ftl::config::create<ftl::operators::Graph>(root, "render_pipe");
-	renderpipe->append<ftl::operators::ColourChannels>("colour");  // Generate interpolation texture...
-	renderpipe->append<ftl::operators::FXAA>("antialiasing"); 
 
-	vs->onRender([vs, &groups, &renderpipe](ftl::rgbd::Frame &out) {
-		bool hasFrame = false;
-		for (auto &reconstr : groups) {
-			hasFrame = reconstr->render(vs, out) || hasFrame;
-		}
+			auto reconstr = ftl::create<ftl::Reconstruction>(root, std::string("recon")+std::to_string(i), std::to_string(i));
+			//reconstr->setGenerator(gen);
+			gen->onFrameSet([stream, reconstr](ftl::rgbd::FrameSet &fs) {
+				stream->select(fs.id, Channel::Colour + Channel::Depth);
+				return reconstr->post(fs);
+			});
 
-		if (hasFrame) {
-			renderpipe->apply(out, out, vs, 0);
-			return true;
+			int i = groups.size();
+			reconstr->onFrameSet([sender,i](ftl::rgbd::FrameSet &fs) {
+				fs.id = i;
+				sender->post(fs);
+				return true;
+			});
+			groups.push_back(reconstr);
+			stream->begin();
 		} else {
-			LOG(INFO) << "NO FRAME";
-			return false;
+			return;
 		}
-	});
-	stream->add(vs);
-
-	for (auto c : ftl::config::getChildren(root->getID())) {
-		LOG(INFO) << "Tagging configurable: " << c->getID();
-		auto tags = c->value<std::vector<std::string>>("tags", nlohmann::json::array({}));
-		tags.push_back("reconstruction");
-		c->set("tags", tags);
 	}
 
-	// ---- Recording code -----------------------------------------------------
-	std::ofstream fileout;
-	ftl::codecs::Writer writer(fileout);
-
-	std::ofstream snapshotout;
-	ftl::codecs::Writer snapshotwriter(snapshotout);
-
-	controls->set("record", false);
-
-	int64_t timestamp = -1;
-	bool writingSnapshot = false;
-	std::unordered_set<int64_t> precedingFrames, followingFrames;
-	// Add a recording callback to all reconstruction scenes
-	for (size_t i=0; i<sources.size(); ++i) {
-		sources[i]->addRawCallback([&writer,&groups,&snapshotout,&snapshotwriter,&timestamp,&writingSnapshot,&precedingFrames,&followingFrames,i](ftl::rgbd::Source *src, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
-			ftl::codecs::StreamPacket s = spkt;
-
-			// Patch stream ID to match order in group
-			s.streamID = i;
-			writer.write(s, pkt);
-
-			if (snapshotwriter.active()) {
-				// The frame that is captured is the next IFrame, unless that
-				// IFrame is one of the first two frames seen. In this case a
-				// part of the frame might already have been missed, so the
-				// IFrame after that one is captured instead.
-
-				// Write all pose and calibration packets.
-				if ((int)spkt.channel >= 64) {
-					snapshotwriter.write(s, pkt);
-				} else if (precedingFrames.size() >= 2) {
-					bool isIFrame = true;
-					switch (pkt.codec) {
-						case ftl::codecs::codec_t::H264:
-							isIFrame = ftl::codecs::h264::isIFrame(pkt.data);
-							break;
-						case ftl::codecs::codec_t::HEVC:
-							isIFrame = ftl::codecs::hevc::isIFrame(pkt.data);
-					}
-
-					if (isIFrame && precedingFrames.count(s.timestamp) == 0) {
-						timestamp = s.timestamp;
-						writingSnapshot = true;
-						snapshotwriter.write(s, pkt);
-					} else if (writingSnapshot && s.timestamp > timestamp) {
-						followingFrames.insert(s.timestamp);
-					}
-
-					// Keep looking for packets of the captured frame until
-					// packets from two following frames have been seen.
-					if (followingFrames.size() >= 2) {
-						snapshotwriter.end();
-						snapshotout.close();
-					}
-				} else {
-					precedingFrames.insert(s.timestamp);
-				}
-			}
-		});
-	}
-
-	// Allow stream recording
-	controls->on("record", [&fileout,&writer,&sources](const ftl::config::Event &e) {
-		if (e.entity->value("record", false)) {
-			char timestamp[18];
-			std::time_t t=std::time(NULL);
-			std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
-			fileout.open(e.entity->value<std::string>("record-name", std::string(timestamp) + ".ftl"));
-
-			writer.begin();
-
-			// TODO: Write pose+calibration+config packets
-
-			for (size_t i=0; i<sources.size(); ++i) {
-				//writeSourceProperties(writer, i, sources[i]);
-				sources[i]->inject(Channel::Calibration, sources[i]->parameters(), Channel::Left, sources[i]->getCapabilities());
-				sources[i]->inject(sources[i]->getPose());
-			}
-		} else {
-			writer.end();
-			fileout.close();
-		}
-	});
-
-	controls->on("3D-snapshot", [&snapshotout,&snapshotwriter,&writingSnapshot,&precedingFrames,&followingFrames,&sources](const ftl::config::Event &e) {
-		if (!snapshotwriter.active()) {
-			char timestamp[18];
-			std::time_t t=std::time(NULL);
-			std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
-			snapshotout.open(e.entity->value<std::string>("3D-snapshot", std::string(timestamp) + ".ftl"));
-			writingSnapshot = false;
-			precedingFrames.clear();
-			followingFrames.clear();
-			snapshotwriter.begin();
-
-			for (size_t i=0; i<sources.size(); ++i) {
-				//writeSourceProperties(writer, i, sources[i]);
-				sources[i]->inject(Channel::Calibration, sources[i]->parameters(), Channel::Left, sources[i]->getCapabilities());
-				sources[i]->inject(sources[i]->getPose());
-			}
-		}
-	});
-
-	// -------------------------------------------------------------------------
-	
-	//stream->add(group);
-	stream->run();
 
 	LOG(INFO) << "Start timer";
 	ftl::timer::start(true);
@@ -355,8 +260,8 @@ static void run(ftl::Configurable *root) {
 
 	LOG(INFO) << "Deleting...";
 
-	delete stream;
-	delete vs;
+	delete sender;
+	delete outstream;
 	delete net;
 	for (auto g : groups) {
 		delete g;
diff --git a/applications/reconstruct/src/reconstruction.cpp b/applications/reconstruct/src/reconstruction.cpp
index 621693f0ec2d1ea9b1b734e107aa1689b84872db..11e3ca6537ef2a1e989270406750be0e4f7a2ca5 100644
--- a/applications/reconstruct/src/reconstruction.cpp
+++ b/applications/reconstruct/src/reconstruction.cpp
@@ -26,11 +26,9 @@ static Eigen::Affine3d create_rotation_matrix(float ax, float ay, float az) {
 
 Reconstruction::Reconstruction(nlohmann::json &config, const std::string name) :
 	ftl::Configurable(config), busy_(false), rbusy_(false), new_frame_(false), fs_render_(), fs_align_() {
-	group_ = new ftl::rgbd::Group;
-	group_->setName("Reconstruction-" + name);
-	group_->setLatency(4);
+	gen_ = nullptr;
 
-	renderer_ = ftl::create<ftl::render::Triangular>(this, "renderer", &fs_render_);
+	//renderer_ = ftl::create<ftl::render::Triangular>(this, "renderer", &fs_render_);
 
 	pipeline_ = ftl::config::create<ftl::operators::Graph>(this, "pre_filters");
 	pipeline_->append<ftl::operators::DepthChannel>("depth");  // Ensure there is a depth channel
@@ -45,70 +43,82 @@ Reconstruction::Reconstruction(nlohmann::json &config, const std::string name) :
 	pipeline_->append<ftl::operators::CrossSupport>("cross");
 	pipeline_->append<ftl::operators::DiscontinuityMask>("discontinuity");
 	pipeline_->append<ftl::operators::CrossSupport>("cross2")->set("discon_support", true);
-	pipeline_->append<ftl::operators::CullDiscontinuity>("remove_discontinuity");
+	pipeline_->append<ftl::operators::CullDiscontinuity>("remove_discontinuity")->set("enabled", false);
 	//pipeline_->append<ftl::operators::AggreMLS>("mls");  // Perform MLS (using smoothing channel)
 	pipeline_->append<ftl::operators::VisCrossSupport>("viscross")->set("enabled", false);
 	pipeline_->append<ftl::operators::MultiViewMLS>("mvmls");
 
-	group_->sync([this](ftl::rgbd::FrameSet &fs) -> bool {
-		// TODO: pause
-		
-		/*if (busy_) {
-			LOG(WARNING) << "Group frameset dropped: " << fs.timestamp;
-			return true;
-		}
-		busy_ = true;*/
+	//pipeline_->set("enabled", false);
+}
 
-		// Swap the entire frameset to allow rapid return
-		// FIXME: This gets stuck on a mutex
-		//fs.swapTo(fs_align_);
+Reconstruction::~Reconstruction() {
+	// TODO delete
+}
 
-		//ftl::pool.push([this](int id) {
-			//UNIQUE_LOCK(fs.mtx, lk);
+size_t Reconstruction::size() {
+	UNIQUE_LOCK(exchange_mtx_, lk);
+	return fs_align_.frames.size();
+}
 
-			pipeline_->apply(fs, fs, 0);
-			
-			// TODO: To use second GPU, could do a download, swap, device change,
-			// then upload to other device. Or some direct device-2-device copy.
 
-			// FIXME: Need a buffer of framesets
-			/*if (rbusy_) {
-				LOG(WARNING) << "Render frameset dropped: " << fs_align_.timestamp;
-				busy_ = false;
-				//return;
-			}
-			rbusy_ = true;*/
+ftl::rgbd::FrameState &Reconstruction::state(int ix) {
+	UNIQUE_LOCK(exchange_mtx_, lk);
+	if (ix >= 0 && ix < fs_align_.frames.size()) {
+		return *fs_align_.frames[ix].origin();
+	}
+	throw ftl::exception("State index out-of-bounds");
+}
 
-			//LOG(INFO) << "Align complete... " << fs.timestamp;
+void Reconstruction::onFrameSet(const ftl::rgbd::VideoCallback &cb) {
+	cb_ = cb;
+}
 
-			// TODO: Reduce mutex locking problem here...
-			{
-				UNIQUE_LOCK(exchange_mtx_, lk);
-				if (new_frame_ == true) LOG(WARNING) << "Frame lost";
-				fs.swapTo(fs_align_);
-				new_frame_ = true;
-			}
+bool Reconstruction::post(ftl::rgbd::FrameSet &fs) {
+	pipeline_->apply(fs, fs, 0);
+		
+	{
+		UNIQUE_LOCK(exchange_mtx_, lk);
+		if (new_frame_ == true) LOG(WARNING) << "Frame lost";
+		fs.swapTo(fs_align_);
+		new_frame_ = true;
+	}
 
-			//fs.resetFull();
+	if (cb_) {
+		ftl::pool.push([this](int id) {
+			if (new_frame_) {
+				{
+					UNIQUE_LOCK(exchange_mtx_, lk);
+					new_frame_ = false;
+					fs_align_.swapTo(fs_render_);
+				}
 
-			//busy_ = false;
-		//});
-		return true;
-	});
+				if (cb_) cb_(fs_render_);
+			}
+		});
+	}
+
+	return true;
 }
 
-Reconstruction::~Reconstruction() {
-	// TODO delete
+void Reconstruction::setGenerator(ftl::rgbd::Generator *gen) {
+	if (gen_) {
+		throw ftl::exception("Reconstruction already has generator");
+	}
+
+	gen_ = gen;
+	gen_->onFrameSet([this](ftl::rgbd::FrameSet &fs) -> bool {
+		return post(fs);
+	});
 }
 
-void Reconstruction::addSource(ftl::rgbd::Source *src) {
+/*void Reconstruction::addSource(ftl::rgbd::Source *src) {
 	//src->setChannel(Channel::Depth);
 	group_->addSource(src); // TODO: check if source is already in group?
 }
 
 void Reconstruction::addRawCallback(const std::function<void(ftl::rgbd::Source *src, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt)> &cb) {
 	group_->addRawCallback(cb);
-}
+}*/
 
 bool Reconstruction::render(ftl::rgbd::VirtualSource *vs, ftl::rgbd::Frame &out) {
 	{
@@ -145,7 +155,7 @@ bool Reconstruction::render(ftl::rgbd::VirtualSource *vs, ftl::rgbd::Frame &out)
 	//}
 
 	Eigen::Affine3d sm = Eigen::Affine3d(Eigen::Scaling(double(value("scale", 1.0f))));
-	bool res = renderer_->render(vs, out, sm.matrix() * transform);
+	bool res = false; //renderer_->render(vs, out, sm.matrix() * transform);
 	//fs_render_.resetFull();
 	return res;
 }
\ No newline at end of file
diff --git a/applications/reconstruct/src/reconstruction.hpp b/applications/reconstruct/src/reconstruction.hpp
index cb3bb8ebd4c6cca57e1f678b155fa40e95fbcfae..5c2648b3e975209239ac0f2ab38ce9bae014e8cf 100644
--- a/applications/reconstruct/src/reconstruction.hpp
+++ b/applications/reconstruct/src/reconstruction.hpp
@@ -11,20 +11,36 @@
 
 namespace ftl {
 
-class Reconstruction : public ftl::Configurable {
+class Reconstruction : public ftl::Configurable, public ftl::rgbd::Generator {
 	public:
 	Reconstruction(nlohmann::json &config, const std::string name);
 	~Reconstruction();
 
-	void addSource(ftl::rgbd::Source *);
+	//void addSource(ftl::rgbd::Source *);
 
-	void addRawCallback(const std::function<void(ftl::rgbd::Source *src, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt)> &cb);
+	//void addRawCallback(const std::function<void(ftl::rgbd::Source *src, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt)> &cb);
+
+	void setGenerator(ftl::rgbd::Generator *);
 
 	/**
 	 * Do the render for a specified virtual camera.
 	 */
 	bool render(ftl::rgbd::VirtualSource *vs, ftl::rgbd::Frame &out);
 
+	/** Number of frames in last frameset. This can change over time. */
+	size_t size() override;
+
+	/**
+	 * Get the persistent state object for a frame. An exception is thrown
+	 * for a bad index.
+	 */
+	ftl::rgbd::FrameState &state(int ix) override;
+
+	/** Register a callback to receive new frame sets. */
+	void onFrameSet(const ftl::rgbd::VideoCallback &) override;
+
+	bool post(ftl::rgbd::FrameSet &fs);
+
 	private:
 	bool busy_;
 	bool rbusy_;
@@ -33,10 +49,12 @@ class Reconstruction : public ftl::Configurable {
 	
 	ftl::rgbd::FrameSet fs_render_;
 	ftl::rgbd::FrameSet fs_align_;
-	ftl::rgbd::Group *group_;
+	ftl::rgbd::Generator *gen_;
 	ftl::operators::Graph *pipeline_;
 	ftl::render::Triangular *renderer_;
 
+	ftl::rgbd::VideoCallback cb_;
+
 	std::vector<cv::cuda::GpuMat> rgb_;
 };
 
diff --git a/applications/recorder/src/main.cpp b/applications/recorder/src/main.cpp
index d2001114f8f98c4bf35630c9676651bf6e91fae3..267c03feb31653ef62733166ddfcd57ca3fb0f5a 100644
--- a/applications/recorder/src/main.cpp
+++ b/applications/recorder/src/main.cpp
@@ -185,15 +185,13 @@ static void run(ftl::Configurable *root) {
 
 	// -------------------------------------------------------------------------
 
-	stream->setLatency(6);  // FIXME: This depends on source!?
 	stream->add(group);
 	stream->run();
 
 	bool busy = false;
 
-	group->setLatency(4);
 	group->setName("ReconGroup");
-	group->sync([](ftl::rgbd::FrameSet &fs) -> bool {
+	group->onFrameSet([](ftl::rgbd::FrameSet &fs) -> bool {
 		return true;
 	});
 
diff --git a/applications/vision/CMakeLists.txt b/applications/vision/CMakeLists.txt
index 1753488f3407b74518efad555ecdd87be54ba1e9..14fc1ad0077d633121878e9399d2187e21e44648 100644
--- a/applications/vision/CMakeLists.txt
+++ b/applications/vision/CMakeLists.txt
@@ -21,6 +21,6 @@ set_property(TARGET ftl-vision PROPERTY CUDA_SEPARABLE_COMPILATION OFF)
 endif()
 
 #target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include)
-target_link_libraries(ftl-vision ftlrgbd ftlcommon ftlctrl ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} ftlnet)
+target_link_libraries(ftl-vision ftlrgbd ftlcommon ftlstreams ftlctrl ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} ftlnet)
 
 
diff --git a/applications/vision/src/main.cpp b/applications/vision/src/main.cpp
index 058d3f98913cd60bf341cd23fef98901b06f80c0..da166f00cdc2071150417b9775af3bea49eb01a0 100644
--- a/applications/vision/src/main.cpp
+++ b/applications/vision/src/main.cpp
@@ -8,7 +8,6 @@
 #include <loguru.hpp>
 #include <ftl/configuration.hpp>
 #include <ctpl_stl.h>
-// #include <zlib.h>
 
 #include <string>
 #include <map>
@@ -19,13 +18,14 @@
 #include <opencv2/opencv.hpp>
 #include <ftl/rgbd.hpp>
 #include <ftl/middlebury.hpp>
-//#include <ftl/display.hpp>
-#include <ftl/rgbd/streamer.hpp>
 #include <ftl/net/universe.hpp>
 #include <ftl/master.hpp>
 #include <nlohmann/json.hpp>
 #include <ftl/operators/disparity.hpp>
 
+#include <ftl/streams/netstream.hpp>
+#include <ftl/streams/sender.hpp>
+
 #include "opencv2/imgproc.hpp"
 #include "opencv2/imgcodecs.hpp"
 #include "opencv2/highgui.hpp"
@@ -37,8 +37,7 @@
 
 using ftl::rgbd::Source;
 using ftl::rgbd::Camera;
-//using ftl::Display;
-using ftl::rgbd::Streamer;
+using ftl::codecs::Channel;
 using ftl::net::Universe;
 using std::string;
 using std::vector;
@@ -58,45 +57,44 @@ static void run(ftl::Configurable *root) {
 	if (paths && (*paths).size() > 0) file = (*paths)[(*paths).size()-1];
 
 	Source *source = nullptr;
-	source = ftl::create<Source>(root, "source");
-
+	source = ftl::create<Source>(root, "source", net);
 	if (file != "") source->set("uri", file);
 	
-	//Display *display = ftl::create<Display>(root, "display", "local");
+	ftl::stream::Sender *sender = ftl::create<ftl::stream::Sender>(root, "sender");
+	ftl::stream::Net *outstream = ftl::create<ftl::stream::Net>(root, "stream", net);
+	outstream->set("uri", outstream->getID());
+	outstream->begin();
+	sender->setStream(outstream);
+
+	auto *grp = new ftl::rgbd::Group();
+	source->setChannel(Channel::Depth);
+	grp->addSource(source);
+
+	grp->onFrameSet([sender](ftl::rgbd::FrameSet &fs) {
+		fs.id = 0;
+		sender->post(fs);
+		return true;
+	});
 	
-	Streamer *stream = ftl::create<Streamer>(root, "stream", net);
-	stream->add(source);
-
 	auto pipeline = ftl::config::create<ftl::operators::Graph>(root, "pipeline");
 	pipeline->append<ftl::operators::DepthChannel>("depth");  // Ensure there is a depth channel
-	stream->group()->addPipeline(pipeline);
+	grp->addPipeline(pipeline);
 	
 	net->start();
 
 	LOG(INFO) << "Running...";
-	/*if (display->hasDisplays()) {
-		stream->run();
-		while (ftl::running && display->active()) {
-			cv::Mat rgb, depth;
-			source->getFrames(rgb, depth);
-			if (!rgb.empty()) display->render(rgb, depth, source->parameters());
-			display->wait(10);
-		}
-	} else {*/
-		stream->run(true);
-	//}
-
 	ftl::timer::start(true);
-
 	LOG(INFO) << "Stopping...";
 	ctrl.stop();
-	stream->stop();
+	
 	net->shutdown();
 
 	ftl::pool.stop();
 
-	delete stream;
-	//delete display;
+	delete grp;
+	delete sender;
+	delete outstream;
+
 	//delete source;  // TODO(Nick) Add ftl::destroy
 	delete net;
 }
@@ -108,15 +106,8 @@ int main(int argc, char **argv) {
 	std::cout << "FTL Vision Node " << FTL_VERSION_LONG << std::endl;
 	auto root = ftl::configure(argc, argv, "vision_default");
 	
-	//config["ftl://vision/default"]["paths"] = paths;
-
-	// Choose normal or middlebury modes
-	//if (config["middlebury"]["dataset"] == "") {
-		std::cout << "Loading..." << std::endl;
-		run(root);
-	//} else {
-	//	ftl::middlebury::test(config);
-	//}
+	std::cout << "Loading..." << std::endl;
+	run(root);
 
 	delete root;
 	LOG(INFO) << "Terminating with code " << ftl::exit_code;
diff --git a/components/codecs/include/ftl/codecs/channels.hpp b/components/codecs/include/ftl/codecs/channels.hpp
index dca2eef7672a772ac9f22b2a09d42d3ee9e1d9ed..5eacf6a2c1a2ad2540517fb28f9497adf8323796 100644
--- a/components/codecs/include/ftl/codecs/channels.hpp
+++ b/components/codecs/include/ftl/codecs/channels.hpp
@@ -40,7 +40,9 @@ enum struct Channel : int {
 	Configuration	= 64,	// JSON Data
 	Calibration		= 65,	// Camera Parameters Object
 	Pose			= 66,	// Eigen::Matrix4d
-	Index           = 67,
+	Calibration2	= 67,	// Right camera parameters
+	Index           = 68,
+	Control			= 69,	// For stream and encoder control
 	Data			= 2048	// Custom data, any codec.
 };
 
@@ -51,14 +53,15 @@ inline bool isData(Channel c) { return (int)c >= 64; };
 std::string name(Channel c);
 int type(Channel c);
 
+template <int BASE=0>
 class Channels {
 	public:
 
 	class iterator {
 		public:
-		iterator(const Channels &c, unsigned int ix) : channels_(c), ix_(ix) { }
-		iterator operator++();
-		iterator operator++(int junk);
+		iterator(const Channels &c, unsigned int ix) : channels_(c), ix_(ix) { while (ix_ < 32+BASE && !channels_.has(ix_)) ++ix_; }
+		inline iterator operator++() { Channels::iterator i = *this; while (++ix_ < 32+BASE && !channels_.has(ix_)); return i; }
+		inline iterator operator++(int junk) { while (++ix_ < 32+BASE && !channels_.has(ix_)); return *this; }
 		inline ftl::codecs::Channel operator*() { return static_cast<Channel>(static_cast<int>(ix_)); }
 		//ftl::codecs::Channel operator->() { return ptr_; }
 		inline bool operator==(const iterator& rhs) { return ix_ == rhs.ix_; }
@@ -68,41 +71,47 @@ class Channels {
 		unsigned int ix_;
 	};
 
-	inline Channels() { mask = 0; }
-	inline explicit Channels(unsigned int m) { mask = m; }
-	inline explicit Channels(Channel c) { mask = (c == Channel::None) ? 0 : 0x1 << static_cast<unsigned int>(c); }
-	inline Channels &operator=(Channel c) { mask = (c == Channel::None) ? 0 : 0x1 << static_cast<unsigned int>(c); return *this; }
-	inline Channels operator|(Channel c) const { return (c == Channel::None) ? Channels(mask) : Channels(mask | (0x1 << static_cast<unsigned int>(c))); }
-	inline Channels operator+(Channel c) const { return (c == Channel::None) ? Channels(mask) : Channels(mask | (0x1 << static_cast<unsigned int>(c))); }
-	inline Channels &operator|=(Channel c) { mask |= (c == Channel::None) ? 0 : (0x1 << static_cast<unsigned int>(c)); return *this; }
-	inline Channels &operator+=(Channel c) { mask |= (c == Channel::None) ? 0 : (0x1 << static_cast<unsigned int>(c)); return *this; }
-	inline Channels &operator-=(Channel c) { mask &= ~((c == Channel::None) ? 0 : (0x1 << static_cast<unsigned int>(c))); return *this; }
-	inline Channels &operator+=(unsigned int c) { mask |= (0x1 << c); return *this; }
-	inline Channels &operator-=(unsigned int c) { mask &= ~(0x1 << c); return *this; }
+	inline Channels() : mask(0) { }
+	inline explicit Channels(unsigned int m) : mask(m) { }
+	inline explicit Channels(Channel c) : mask((c == Channel::None || static_cast<unsigned int>(c) - BASE >= 32) ? 0 : 0x1 << (static_cast<unsigned int>(c) - BASE)) { }
+	inline Channels(const Channels<BASE> &c) : mask((unsigned int)c.mask) {}
+	//inline Channels(Channels<BASE> &c) : mask((unsigned int)c.mask) {}
+	inline Channels &operator=(const Channels &c) { mask = (unsigned int)c.mask; return *this; }
+	inline Channels &operator=(Channel c) { mask = (c == Channel::None || static_cast<unsigned int>(c) - BASE >= 32) ? 0 : 0x1 << (static_cast<unsigned int>(c) - BASE); return *this; }
+	inline Channels operator|(Channel c) const { return (c == Channel::None || static_cast<unsigned int>(c) - BASE >= 32) ? Channels(mask) : Channels(mask | (0x1 << (static_cast<unsigned int>(c) - BASE))); }
+	inline Channels operator+(Channel c) const { return (c == Channel::None || static_cast<unsigned int>(c) - BASE >= 32) ? Channels(mask) : Channels(mask | (0x1 << (static_cast<unsigned int>(c) - BASE))); }
+	inline Channels &operator|=(Channel c) { mask |= (c == Channel::None || static_cast<unsigned int>(c) - BASE >= 32) ? 0 : (0x1 << (static_cast<unsigned int>(c) - BASE)); return *this; }
+	inline Channels &operator+=(Channel c) { mask |= (c == Channel::None || static_cast<unsigned int>(c) - BASE >= 32) ? 0 : (0x1 << (static_cast<unsigned int>(c) - BASE)); return *this; }
+	inline Channels &operator-=(Channel c) { mask &= ~((c == Channel::None || static_cast<unsigned int>(c) - BASE >= 32) ? 0 : (0x1 << (static_cast<unsigned int>(c) - BASE))); return *this; }
+	inline Channels &operator+=(unsigned int c) { mask |= (0x1 << (c - BASE)); return *this; }
+	inline Channels &operator-=(unsigned int c) { mask &= ~(0x1 << (c - BASE)); return *this; }
+	inline Channels &operator&=(const Channels<BASE> &c) { mask &= c.mask; return *this; }
+	inline Channels operator&(const Channels<BASE> &c) const { return Channels<BASE>(mask & c.mask); }
 
 	inline bool has(Channel c) const {
-		return (c == Channel::None) ? true : mask & (0x1 << static_cast<unsigned int>(c));
+		return (c == Channel::None || static_cast<unsigned int>(c) - BASE >= 32) ? true : mask & (0x1 << (static_cast<unsigned int>(c) - BASE));
 	}
 
 	inline bool has(unsigned int c) const {
-		return mask & (0x1 << c);
+		return mask & (0x1 << (c - BASE));
 	}
 
-	inline iterator begin() { return iterator(*this, 0); }
-	inline iterator end() { return iterator(*this, 32); }
+	inline iterator begin() { return iterator(*this, BASE); }
+	inline iterator end() { return iterator(*this, 32+BASE); }
 
-	inline operator unsigned int() { return mask; }
-	inline operator bool() { return mask > 0; }
-	inline operator Channel() {
+	inline bool operator==(const Channels<BASE> &c) const { return mask == c.mask; }
+	inline operator unsigned int() const { return mask; }
+	inline operator bool() const { return mask > 0; }
+	inline operator Channel() const {
 		if (mask == 0) return Channel::None;
 		int ix = 0;
 		int tmask = mask;
 		while (!(tmask & 0x1) && ++ix < 32) tmask >>= 1;
-		return static_cast<Channel>(ix);
+		return static_cast<Channel>(ix + BASE);
 	}
 	
-	inline size_t count() { return std::bitset<32>(mask).count(); }
-	inline bool empty() { return mask == 0; }
+	inline size_t count() const { return std::bitset<32>(mask).count(); }
+	inline bool empty() const { return mask == 0; }
 	inline void clear() { mask = 0; }
 
 	static const size_t kMax = 32;
@@ -110,14 +119,12 @@ class Channels {
 	static Channels All();
 
 	private:
-	unsigned int mask;
+	std::atomic<unsigned int> mask;
 };
 
-inline Channels::iterator Channels::iterator::operator++() { Channels::iterator i = *this; while (++ix_ < 32 && !channels_.has(ix_)); return i; }
-inline Channels::iterator Channels::iterator::operator++(int junk) { while (++ix_ < 32 && !channels_.has(ix_)); return *this; }
-
-inline Channels Channels::All() {
-	return Channels(0xFFFFFFFFu);
+template <int BASE>
+inline Channels<BASE> Channels<BASE>::All() {
+	return Channels<BASE>(0xFFFFFFFFu);
 }
 
 static const Channels kNoChannels;
@@ -140,12 +147,14 @@ inline bool isFloatChannel(ftl::codecs::Channel chan) {
 
 MSGPACK_ADD_ENUM(ftl::codecs::Channel);
 
-inline ftl::codecs::Channels operator|(ftl::codecs::Channel a, ftl::codecs::Channel b) {
-	return ftl::codecs::Channels(a) | b;
+template <int BASE=0>
+inline ftl::codecs::Channels<BASE> operator|(ftl::codecs::Channel a, ftl::codecs::Channel b) {
+	return ftl::codecs::Channels<BASE>(a) | b;
 }
 
-inline ftl::codecs::Channels operator+(ftl::codecs::Channel a, ftl::codecs::Channel b) {
-	return ftl::codecs::Channels(a) | b;
+template <int BASE=0>
+inline ftl::codecs::Channels<BASE> operator+(ftl::codecs::Channel a, ftl::codecs::Channel b) {
+	return ftl::codecs::Channels<BASE>(a) | b;
 }
 
 #endif  // _FTL_RGBD_CHANNELS_HPP_
diff --git a/components/codecs/include/ftl/codecs/encoder.hpp b/components/codecs/include/ftl/codecs/encoder.hpp
index ed817f7b1c5a59b133c36317195a5c2da9203e56..ae2d69f9d1fa549bcc85d456d67fe1c5930704dc 100644
--- a/components/codecs/include/ftl/codecs/encoder.hpp
+++ b/components/codecs/include/ftl/codecs/encoder.hpp
@@ -12,6 +12,7 @@ namespace ftl {
 namespace codecs {
 
 static const unsigned int kVideoBufferSize = 10*1024*1024;
+static constexpr uint8_t kFlagMultiple = 0x80;
 
 class Encoder;
 
diff --git a/components/codecs/include/ftl/codecs/h264.hpp b/components/codecs/include/ftl/codecs/h264.hpp
index 17f649c52220f7ac8b00e6d4ed9b78e18f2847f8..c4eddd7c10c4871246ecd3de5d66c6591a2332db 100644
--- a/components/codecs/include/ftl/codecs/h264.hpp
+++ b/components/codecs/include/ftl/codecs/h264.hpp
@@ -51,16 +51,16 @@ enum class NALType : int {
  * Extract the NAL unit type from the first NAL header.
  * With NvPipe, the 5th byte contains the NAL Unit header.
  */
-inline NALType getNALType(const std::vector<uint8_t> &data) {
-	return static_cast<NALType>(data[4] & 0x1F);
+inline NALType getNALType(const unsigned char *data, size_t size) {
+	return (size > 4) ? static_cast<NALType>(data[4] & 0x1F) : NALType::UNSPECIFIED_0;
 }
 
 /**
  * Check the H264 bitstream for an I-Frame. With NvPipe, all I-Frames start
  * with a SPS NAL unit so just check for this.
  */
-inline bool isIFrame(const std::vector<uint8_t> &data) {
-	return getNALType(data) == NALType::SPS;
+inline bool isIFrame(const unsigned char *data, size_t size) {
+	return getNALType(data, size) == NALType::SPS;
 }
 
 }
diff --git a/components/codecs/include/ftl/codecs/hevc.hpp b/components/codecs/include/ftl/codecs/hevc.hpp
index b3a32246544f3cf24a4ad09345c2f47a96eb0735..fa436d9904f098bb39556a27f3ea5b21a2963873 100644
--- a/components/codecs/include/ftl/codecs/hevc.hpp
+++ b/components/codecs/include/ftl/codecs/hevc.hpp
@@ -93,20 +93,20 @@ enum class NALType : int {
  * Extract the NAL unit type from the first NAL header.
  * With NvPipe, the 5th byte contains the NAL Unit header.
  */
-inline NALType getNALType(const std::vector<uint8_t> &data) {
-	return static_cast<NALType>((data[4] >> 1) & 0x3F);
+inline NALType getNALType(const unsigned char *data, size_t size) {
+	return (size > 4) ? static_cast<NALType>((data[4] >> 1) & 0x3F) : NALType::INVALID;
 }
 
-inline bool validNAL(const std::vector<uint8_t> &data) {
-	return data[0] == 0 && data[1] == 0 && data[2] == 0 && data[3] == 1;
+inline bool validNAL(const unsigned char *data, size_t size) {
+	return size > 4 && data[0] == 0 && data[1] == 0 && data[2] == 0 && data[3] == 1;
 }
 
 /**
  * Check the HEVC bitstream for an I-Frame. With NvPipe, all I-Frames start
  * with a VPS NAL unit so just check for this.
  */
-inline bool isIFrame(const std::vector<uint8_t> &data) {
-	return getNALType(data) == NALType::VPS;
+inline bool isIFrame(const unsigned char *data, size_t size) {
+	return getNALType(data, size) == NALType::VPS;
 }
 
 }
diff --git a/components/codecs/include/ftl/codecs/nvpipe_decoder.hpp b/components/codecs/include/ftl/codecs/nvpipe_decoder.hpp
index 78d6d7fea43cf729c3034ed63c173ca1a8cc9797..82a61feaed8a34d7d00d1fa00d127a8c0c2361f4 100644
--- a/components/codecs/include/ftl/codecs/nvpipe_decoder.hpp
+++ b/components/codecs/include/ftl/codecs/nvpipe_decoder.hpp
@@ -27,6 +27,8 @@ class NvPipeDecoder : public ftl::codecs::Decoder {
 	bool seen_iframe_;
 	cv::cuda::GpuMat tmp_;
 	cv::cuda::Stream stream_;
+
+	bool _checkIFrame(ftl::codecs::codec_t codec, const unsigned char *data, size_t size);
 };
 
 }
diff --git a/components/codecs/include/ftl/codecs/packet.hpp b/components/codecs/include/ftl/codecs/packet.hpp
index f2fa53c8fa860527f1939b61c4962b306da89043..0788653c1b95781d56ba5fd419b6f8eacb7948f5 100644
--- a/components/codecs/include/ftl/codecs/packet.hpp
+++ b/components/codecs/include/ftl/codecs/packet.hpp
@@ -11,12 +11,15 @@
 namespace ftl {
 namespace codecs {
 
+static constexpr uint8_t kAllFrames = 255;
+static constexpr uint8_t kAllFramesets = 255;
+
 /**
  * First bytes of our file format.
  */
 struct Header {
 	const char magic[4] = {'F','T','L','F'};
-	uint8_t version = 3;
+	uint8_t version = 4;
 };
 
 /**
@@ -34,13 +37,22 @@ struct IndexHeader {
  */
 struct Packet {
 	ftl::codecs::codec_t codec;
-	ftl::codecs::definition_t definition;
-	uint8_t block_total;	// Packets expected per frame
-	uint8_t block_number; 	// This packets number within a frame
+	ftl::codecs::definition_t definition;	// Data resolution
+
+	union {
+	[[deprecated]] uint8_t block_total;	// v1-3 Packets expected per frame
+	uint8_t frame_count;	// v4+ Frames included in this packet
+	};
+
+	union {
+	[[deprecated]] uint8_t block_number; 	// v1-3 This packets number within a frame
+	uint8_t bitrate=0;	// v4+ For multi-bitrate encoding, 0=highest
+	};
+
 	uint8_t flags;			// Codec dependent flags (eg. I-Frame or P-Frame)
 	std::vector<uint8_t> data;
 
-	MSGPACK_DEFINE(codec, definition, block_total, block_number, flags, data);
+	MSGPACK_DEFINE(codec, definition, frame_count, bitrate, flags, data);
 };
 
 /**
@@ -49,12 +61,24 @@ struct Packet {
  * or included before a frame packet structure.
  */
 struct StreamPacket {
+	int version;			// FTL version, Not encoded into stream
+
 	int64_t timestamp;
-	uint8_t streamID;  		// Source number... 255 = broadcast stream
-	uint8_t channel_count;	// Number of channels to expect for this frame to complete (usually 1 or 2)
+	uint8_t streamID;  		// Source number [or v4 frameset id]
+
+	union {
+		[[deprecated]] uint8_t channel_count;	// v1-3 Number of channels to expect for this frame(set) to complete (usually 1 or 2)
+		uint8_t frame_number;	// v4+ First frame number (packet may include multiple frames)
+	};
+
 	ftl::codecs::Channel channel;		// Actual channel of this current set of packets
 
-	MSGPACK_DEFINE(timestamp, streamID, channel_count, channel);
+	inline int frameNumber() const { return (version >= 4) ? frame_number : streamID; }
+	inline int frameSetID() const { return (version >= 4) ? streamID : 0; }
+
+	MSGPACK_DEFINE(timestamp, streamID, frame_number, channel);
+
+	operator std::string() const;
 };
 
 /**
diff --git a/components/codecs/src/bitrates.cpp b/components/codecs/src/bitrates.cpp
index 37889f5a55bf0337d1b3b750538587d1cc81f537..fc101f6bbd5733389db04fc75369b5512c4a3e8e 100644
--- a/components/codecs/src/bitrates.cpp
+++ b/components/codecs/src/bitrates.cpp
@@ -52,7 +52,6 @@ int ftl::codecs::getHeight(definition_t d) {
 
 definition_t ftl::codecs::findDefinition(int width, int height) {
 	int best = 0;
-	bool smaller = true;
 
 	for(const Resolution res : resolutions) {
 		if ((res.width == width) && (res.height == height)) {
diff --git a/components/codecs/src/channels.cpp b/components/codecs/src/channels.cpp
index b33427b70d5048bb707126ed67c94d55f7d3642c..b5b52fecf048902212670b0278d800bb2a32a12f 100644
--- a/components/codecs/src/channels.cpp
+++ b/components/codecs/src/channels.cpp
@@ -8,27 +8,27 @@ struct ChannelInfo {
 };
 
 static ChannelInfo info[] = {
-    "Colour", CV_8UC4,
-    "Depth", CV_32F,
-    "Right", CV_8UC4,
-    "DepthRight", CV_32F,
-    "Deviation", CV_32F,
-    "Normals", CV_32FC4,
-    "Points", CV_32FC4,
-    "Confidence", CV_32F,
-    "EnergyVector", CV_32FC4,
-    "Flow", CV_32F,
-    "Energy", CV_32F,
-	"Mask", CV_32S,
-	"Density", CV_32F,
-    "Support1", CV_8UC4,
-    "Support2", CV_8UC4,
-    "Segmentation", CV_32S,
+    "Colour", CV_8UC4,			// 0
+    "Depth", CV_32F,			// 1
+    "Right", CV_8UC4,			// 2
+    "DepthRight", CV_32F,		// 3
+    "Deviation", CV_32F,		// 4
+    "Normals", CV_32FC4,		// 5
+    "Points", CV_32FC4,			// 6
+    "Confidence", CV_32F,		// 7
+    "EnergyVector", CV_32FC4,	// 8
+    "Flow", CV_32F,				// 9
+    "Energy", CV_32F,			// 10
+	"Mask", CV_32S,				// 11
+	"Density", CV_32F,			// 12
+    "Support1", CV_8UC4,		// 13
+    "Support2", CV_8UC4,		// 14
+    "Segmentation", CV_32S,		// 15
 
-	"NoName", 0,
-	"NoName", 0,
-	"NoName", 0,
-	"NoName", 0,
+	"ColourNormals", 0,			// 16
+	"ColourHighRes", 0,			// 17
+	"Disparity", 0,				// 18
+	"Smoothing", 0,				// 19
 	"NoName", 0,
 	"NoName", 0,
 	"NoName", 0,
diff --git a/components/codecs/src/decoder.cpp b/components/codecs/src/decoder.cpp
index 399e0787f2949d54b5c180c3626f3e616470ced1..3396233f18e294f130d27bebf632413ba050d831 100644
--- a/components/codecs/src/decoder.cpp
+++ b/components/codecs/src/decoder.cpp
@@ -5,6 +5,14 @@
 
 using ftl::codecs::Decoder;
 using ftl::codecs::codec_t;
+using std::string;
+using std::to_string;
+
+ftl::codecs::StreamPacket::operator std::string() const {
+	return string("[\n  timestamp=") + to_string(timestamp) + string(",\n  frameset=") +
+		to_string(streamID) + string(",\n  frame=") + to_string(frame_number) +
+		string(",\n  channel=") + to_string((int)channel) + string("\n]");
+}
 
 Decoder *ftl::codecs::allocateDecoder(const ftl::codecs::Packet &pkt) {
 	switch(pkt.codec) {
diff --git a/components/codecs/src/nvpipe_decoder.cpp b/components/codecs/src/nvpipe_decoder.cpp
index d24d903d076db1c471b921b5591a4ac3771c9d85..605586990f7dc048232e2539c104b304a311decf 100644
--- a/components/codecs/src/nvpipe_decoder.cpp
+++ b/components/codecs/src/nvpipe_decoder.cpp
@@ -1,4 +1,5 @@
 #include <ftl/codecs/nvpipe_decoder.hpp>
+#include <ftl/codecs/nvpipe_encoder.hpp>
 
 #include <loguru.hpp>
 
@@ -24,6 +25,24 @@ NvPipeDecoder::~NvPipeDecoder() {
 	}
 }
 
+template <typename T>
+static T readValue(const unsigned char **data) {
+	const T *ptr = (const T*)(*data);
+	*data += sizeof(T);
+	return *ptr;
+}
+
+bool NvPipeDecoder::_checkIFrame(ftl::codecs::codec_t codec, const unsigned char *data, size_t size) {
+	if (!seen_iframe_) {
+		if (codec == ftl::codecs::codec_t::HEVC || codec == ftl::codecs::codec_t::HEVC_LOSSLESS) {
+			if (ftl::codecs::hevc::isIFrame(data, size)) seen_iframe_ = true;
+		} else if (codec == ftl::codecs::codec_t::H264 || codec == ftl::codecs::codec_t::H264_LOSSLESS) {
+			if (ftl::codecs::h264::isIFrame(data, size)) seen_iframe_ = true;
+		}
+	}
+	return seen_iframe_;
+}
+
 bool NvPipeDecoder::decode(const ftl::codecs::Packet &pkt, cv::cuda::GpuMat &out) {
 	cudaSetDevice(0);
 	UNIQUE_LOCK(mutex_,lk);
@@ -42,9 +61,6 @@ bool NvPipeDecoder::decode(const ftl::codecs::Packet &pkt, cv::cuda::GpuMat &out
 
 	bool islossless = ((pkt.codec == ftl::codecs::codec_t::HEVC || pkt.codec == ftl::codecs::codec_t::H264) && is_float_frame && !(pkt.flags & 0x2)) || pkt.codec == ftl::codecs::codec_t::HEVC_LOSSLESS || pkt.codec == ftl::codecs::codec_t::H264_LOSSLESS; 
 
-	//LOG(INFO) << "DECODE OUT: " << out.rows << ", " << out.type();
-	//LOG(INFO) << "DECODE RESOLUTION: (" << (int)pkt.definition << ") " << ftl::codecs::getWidth(pkt.definition) << "x" << ftl::codecs::getHeight(pkt.definition);
-
 	// Build a decoder instance of the correct kind
 	if (nv_decoder_ == nullptr) {
 		nv_decoder_ = NvPipe_CreateDecoder(
@@ -62,71 +78,60 @@ bool NvPipeDecoder::decode(const ftl::codecs::Packet &pkt, cv::cuda::GpuMat &out
 	
 	tmp_.create(cv::Size(ftl::codecs::getWidth(pkt.definition),ftl::codecs::getHeight(pkt.definition)), (!is_float_frame) ? CV_8UC4 : (islossless) ? CV_16U : CV_16UC4);
 
-	// Check for an I-Frame
-	if (!seen_iframe_) {
-		if (pkt.codec == ftl::codecs::codec_t::HEVC || pkt.codec == ftl::codecs::codec_t::HEVC_LOSSLESS) {
-			if (ftl::codecs::hevc::isIFrame(pkt.data)) seen_iframe_ = true;
-		} else if (pkt.codec == ftl::codecs::codec_t::H264 || pkt.codec == ftl::codecs::codec_t::H264_LOSSLESS) {
-			if (ftl::codecs::h264::isIFrame(pkt.data)) seen_iframe_ = true;
-		}
-	}
-
-	// No I-Frame yet so don't attempt to decode P-Frames.
-	if (!seen_iframe_) return false;
-
 	// Final checks for validity
 	if (pkt.data.size() == 0 || tmp_.size() != out.size()) { // || !ftl::codecs::hevc::validNAL(pkt.data)) {
 		LOG(ERROR) << "Failed to decode packet";
 		return false;
 	}
 
-	int rc = NvPipe_Decode(nv_decoder_, pkt.data.data(), pkt.data.size(), tmp_.data, tmp_.cols, tmp_.rows, tmp_.step);
-	if (rc == 0) LOG(ERROR) << "NvPipe decode error: " << NvPipe_GetError(nv_decoder_);
+	int rc = 0;
+	if (pkt.flags & ftl::codecs::kFlagMultiple) {
+		const unsigned char *ptr = pkt.data.data();
+		const unsigned char *eptr = ptr+pkt.data.size();
 
-	if (is_float_frame) {
-		// Is the received frame the same size as requested output?
-		//if (out.rows == ftl::codecs::getHeight(pkt.definition)) {
-
-			//tmp_.convertTo(out, CV_32FC1, 1.0f/1000.0f, stream_);
-			
-
-			if (!islossless) {
-				//cv::cuda::cvtColor(tmp_, tmp_, cv::COLOR_RGB2YUV, 4, stream_);
-				/*cv::Mat tmpHost;
-				tmp_.download(tmpHost);
-				cv::imshow("DEPTH", tmpHost);
-				cv::waitKey(1);*/
-				ftl::cuda::vuya_to_depth(out, tmp_, 16.0f, stream_);
-			} else {
-				tmp_.convertTo(out, CV_32FC1, 1.0f/1000.0f, stream_);
+		while (ptr < eptr) {
+			int size = readValue<int>(&ptr);
+
+			// Skip if still missing an IFrame.
+			if (!_checkIFrame(pkt.codec, ptr, size)) {
+				LOG(WARNING) << "P-Frame without I-Frame in decoder";
+				ptr += size;
+				if (ptr < eptr) continue;
+				else return false;
 			}
 
-		/*} else {
-			LOG(WARNING) << "Resizing decoded frame from " << tmp_.size() << " to " << out.size();
-			// FIXME: This won't work on GPU
-			tmp_.convertTo(tmp_, CV_32FC1, 1.0f/1000.0f, stream_);
-			cv::cuda::resize(tmp_, out, out.size(), 0, 0, cv::INTER_NEAREST, stream_);
-		}*/
+			rc = NvPipe_Decode(nv_decoder_, ptr, size, tmp_.data, tmp_.cols, tmp_.rows, tmp_.step);
+			if (rc == 0) LOG(ERROR) << "NvPipe decode error: " << NvPipe_GetError(nv_decoder_);
+			ptr += size;
+		}
+
+		//LOG(WARNING) << "Decode of multiple frames: " << count;
 	} else {
-		// Is the received frame the same size as requested output?
-		//if (out.rows == ftl::codecs::getHeight(pkt.definition)) {
-			// Flag 0x1 means frame is in RGB so needs conversion to BGR
-			if (pkt.flags & 0x1) {
-				cv::cuda::cvtColor(tmp_, out, cv::COLOR_RGBA2BGRA, 0, stream_);
-			} else {
-				//cv::cuda::cvtColor(tmp_, out, cv::COLOR_BGRA2BGR, 0, stream_);
-			}
-		/*} else {
-			LOG(WARNING) << "Resizing decoded frame from " << tmp_.size() << " to " << out.size();
-			// FIXME: This won't work on GPU, plus it allocates extra memory...
-			// Flag 0x1 means frame is in RGB so needs conversion to BGR
-			if (pkt.flags & 0x1) {
-				cv::cuda::cvtColor(tmp_, tmp_, cv::COLOR_RGBA2BGR, 0, stream_);
-			} else {
-				cv::cuda::cvtColor(tmp_, tmp_, cv::COLOR_BGRA2BGR, 0, stream_);
-			}
-			cv::cuda::resize(tmp_, out, out.size(), 0.0, 0.0, cv::INTER_LINEAR, stream_);
-		}*/
+		if (!_checkIFrame(pkt.codec, pkt.data.data(), pkt.data.size())) {
+			LOG(WARNING) << "P-Frame without I-Frame in decoder";
+			return false;
+		}
+		rc = NvPipe_Decode(nv_decoder_, pkt.data.data(), pkt.data.size(), tmp_.data, tmp_.cols, tmp_.rows, tmp_.step);
+		if (rc == 0) LOG(ERROR) << "NvPipe decode error: " << NvPipe_GetError(nv_decoder_);
+	}
+
+	if (is_float_frame) {
+		if (!islossless) {
+			//cv::cuda::cvtColor(tmp_, tmp_, cv::COLOR_RGB2YUV, 4, stream_);
+			/*cv::Mat tmpHost;
+			tmp_.download(tmpHost);
+			cv::imshow("DEPTH", tmpHost);
+			cv::waitKey(1);*/
+
+			ftl::cuda::vuya_to_depth(out, tmp_, 16.0f, stream_);
+		} else {
+			tmp_.convertTo(out, CV_32FC1, 1.0f/1000.0f, stream_);
+		}
+	} else {
+		// Flag 0x1 means frame is in RGB so needs conversion to BGR
+		if (pkt.flags & 0x1) {
+			cv::cuda::cvtColor(tmp_, out, cv::COLOR_RGBA2BGRA, 0, stream_);
+		}
 	}
 
 	stream_.waitForCompletion();
diff --git a/components/codecs/src/nvpipe_encoder.cpp b/components/codecs/src/nvpipe_encoder.cpp
index 720f69d34409b6b9c8f1adc681b5e6be839b25e2..2c3b27636999676578cc7eb34301a6b0fa1757db 100644
--- a/components/codecs/src/nvpipe_encoder.cpp
+++ b/components/codecs/src/nvpipe_encoder.cpp
@@ -123,8 +123,8 @@ bool NvPipeEncoder::encode(const cv::cuda::GpuMat &in, definition_t odefinition,
 	Packet pkt;
 	pkt.codec = preference_;
 	pkt.definition = definition;
-	pkt.block_total = 1;
-	pkt.block_number = 0;
+	pkt.frame_count = 1;
+	pkt.bitrate = 0;
 	pkt.flags = NvPipeEncoder::kFlagRGB | NvPipeEncoder::kFlagMappedDepth;
 
 	pkt.data.resize(ftl::codecs::kVideoBufferSize);
diff --git a/components/codecs/src/opencv_decoder.cpp b/components/codecs/src/opencv_decoder.cpp
index f721b462a17c3fb71f83b7ee4fadbfb1240e81c2..e08117ae2225eed1e3871a0a94383bae05efa67b 100644
--- a/components/codecs/src/opencv_decoder.cpp
+++ b/components/codecs/src/opencv_decoder.cpp
@@ -19,13 +19,13 @@ bool OpenCVDecoder::accepts(const ftl::codecs::Packet &pkt) {
 
 bool OpenCVDecoder::decode(const ftl::codecs::Packet &pkt, cv::cuda::GpuMat &out) {
 	//CHECK(cv::Size(ftl::codecs::getWidth(pkt.definition), ftl::codecs::getHeight(pkt.definition)) == out.size()); 
-	int chunk_dim = std::sqrt(pkt.block_total);
+	int chunk_dim = std::sqrt(pkt.frame_count);
 	int chunk_width = out.cols / chunk_dim;
 	int chunk_height = out.rows / chunk_dim;
 
 	// Build chunk head
-	int cx = (pkt.block_number % chunk_dim) * chunk_width;
-	int cy = (pkt.block_number / chunk_dim) * chunk_height;
+	int cx = 0; //(pkt.block_number % chunk_dim) * chunk_width;
+	int cy = 0; //(pkt.block_number / chunk_dim) * chunk_height;
 	cv::Rect roi(cx,cy,chunk_width,chunk_height);
 	cv::cuda::GpuMat chunkHead = out(roi);
 
diff --git a/components/codecs/src/opencv_encoder.cpp b/components/codecs/src/opencv_encoder.cpp
index 772922e7bf740a1b47ecea2260d448afca217fe0..01310c41f55ba34dfbe08d1b99bdc256a70358cd 100644
--- a/components/codecs/src/opencv_encoder.cpp
+++ b/components/codecs/src/opencv_encoder.cpp
@@ -54,19 +54,19 @@ bool OpenCVEncoder::encode(const cv::cuda::GpuMat &in, definition_t definition,
 	chunk_count_ = chunk_dim_ * chunk_dim_;
 	jobs_ = chunk_count_;
 
-	for (int i=0; i<chunk_count_; ++i) {
+	//for (int i=0; i<chunk_count_; ++i) {
 		// Add chunk job to thread pool
-		ftl::pool.push([this,i,cb,is_colour,bitrate](int id) {
+		//ftl::pool.push([this,i,cb,is_colour,bitrate](int id) {
 			ftl::codecs::Packet pkt;
-			pkt.block_number = i;
-			pkt.block_total = chunk_count_;
+			pkt.bitrate = 0;
+			pkt.frame_count = 1;
 			pkt.definition = current_definition_;
 			pkt.codec = (is_colour) ? codec_t::JPG : codec_t::PNG;
 
 			try {
 				_encodeBlock(tmp_, pkt, bitrate);
 			} catch(...) {
-				LOG(ERROR) << "OpenCV encode block exception: " << i;
+				LOG(ERROR) << "OpenCV encode block exception: ";
 			}
 
 			try {
@@ -75,17 +75,17 @@ bool OpenCVEncoder::encode(const cv::cuda::GpuMat &in, definition_t definition,
 				LOG(ERROR) << "OpenCV encoder callback exception";
 			}
 
-			std::unique_lock<std::mutex> lk(job_mtx_);
-			--jobs_;
-			if (jobs_ == 0) job_cv_.notify_one();
-		});
-	}
+			//std::unique_lock<std::mutex> lk(job_mtx_);
+			//--jobs_;
+			//if (jobs_ == 0) job_cv_.notify_one();
+		//});
+	//}
 
-	std::unique_lock<std::mutex> lk(job_mtx_);
-	job_cv_.wait_for(lk, std::chrono::seconds(20), [this]{ return jobs_ == 0; });
-	if (jobs_ != 0) {
-		LOG(FATAL) << "Deadlock detected (" << jobs_ << ")";
-	}
+	//std::unique_lock<std::mutex> lk(job_mtx_);
+	//job_cv_.wait_for(lk, std::chrono::seconds(20), [this]{ return jobs_ == 0; });
+	//if (jobs_ != 0) {
+	//	LOG(FATAL) << "Deadlock detected (" << jobs_ << ")";
+	//}
 
 	return true;
 }
@@ -95,8 +95,8 @@ bool OpenCVEncoder::_encodeBlock(const cv::Mat &in, ftl::codecs::Packet &pkt, bi
 	int chunk_height = in.rows / chunk_dim_;
 
 	// Build chunk heads
-	int cx = (pkt.block_number % chunk_dim_) * chunk_width;
-	int cy = (pkt.block_number / chunk_dim_) * chunk_height;
+	int cx = 0; //(pkt.block_number % chunk_dim_) * chunk_width;
+	int cy = 0; //(pkt.block_number / chunk_dim_) * chunk_height;
 	cv::Rect roi(cx,cy,chunk_width,chunk_height);
 
 	cv::Mat chunkHead = in(roi);
diff --git a/components/codecs/test/CMakeLists.txt b/components/codecs/test/CMakeLists.txt
index d816a0572aa0935ff8d0896b00d211ad211b839a..63d40a0ea26b09429f98343bbb99de2cf5fcb44e 100644
--- a/components/codecs/test/CMakeLists.txt
+++ b/components/codecs/test/CMakeLists.txt
@@ -33,18 +33,18 @@ target_link_libraries(nvpipe_codec_unit
 add_test(NvPipeCodecUnitTest nvpipe_codec_unit)
 
 ### Reader Writer Unit ################################################################
-add_executable(rw_unit
-	./tests.cpp
-	../src/writer.cpp
-	../src/reader.cpp
-	./readwrite_test.cpp
-)
-target_include_directories(rw_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
-target_link_libraries(rw_unit
-	Threads::Threads ${OS_LIBS} ${OpenCV_LIBS} ${CUDA_LIBRARIES} ftlcommon Eigen3::Eigen)
-
-
-add_test(RWUnitTest rw_unit)
+#add_executable(rw_unit
+#	./tests.cpp
+#	../src/writer.cpp
+#	../src/reader.cpp
+#	./readwrite_test.cpp
+#)
+#target_include_directories(rw_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
+#target_link_libraries(rw_unit
+#	Threads::Threads ${OS_LIBS} ${OpenCV_LIBS} ${CUDA_LIBRARIES} ftlcommon Eigen3::Eigen)
+
+
+#add_test(RWUnitTest rw_unit)
 
 ### Channel Unit ###############################################################
 add_executable(channel_unit
diff --git a/components/codecs/test/readwrite_test.cpp b/components/codecs/test/readwrite_test.cpp
index ea8781b1b3b3f9204a26cd2fca527603ee855596..70c97d82d4aa03ff3ba8524591e93d7de48cd1ca 100644
--- a/components/codecs/test/readwrite_test.cpp
+++ b/components/codecs/test/readwrite_test.cpp
@@ -27,8 +27,8 @@ TEST_CASE( "Write and read - Single frame" ) {
 
 	pkt.codec = codec_t::JSON;
 	pkt.definition = definition_t::Any;
-	pkt.block_number = 0;
-	pkt.block_total = 1;
+	pkt.bitrate = 0;
+	pkt.frame_count = 1;
 	pkt.flags = 0;
 	pkt.data = {44,44,44};
 
@@ -68,8 +68,8 @@ TEST_CASE( "Write and read - Multiple frames" ) {
 
 	pkt.codec = codec_t::JSON;
 	pkt.definition = definition_t::Any;
-	pkt.block_number = 0;
-	pkt.block_total = 1;
+	pkt.bitrate = 0;
+	pkt.frame_count = 1;
 	pkt.flags = 0;
 	pkt.data = {44,44,44};
 
@@ -115,8 +115,8 @@ TEST_CASE( "Write and read - Multiple streams" ) {
 
 	pkt.codec = codec_t::JSON;
 	pkt.definition = definition_t::Any;
-	pkt.block_number = 0;
-	pkt.block_total = 1;
+	pkt.bitrate = 0;
+	pkt.frame_count = 1;
 	pkt.flags = 0;
 	pkt.data = {44,44,44};
 
@@ -170,8 +170,8 @@ TEST_CASE( "Write and read - Multiple frames with limit" ) {
 
 	pkt.codec = codec_t::JSON;
 	pkt.definition = definition_t::Any;
-	pkt.block_number = 0;
-	pkt.block_total = 1;
+	pkt.bitrate = 0;
+	pkt.frame_count = 1;
 	pkt.flags = 0;
 	pkt.data = {44,44,44};
 
@@ -217,8 +217,8 @@ TEST_CASE( "Write and read - Multiple reads" ) {
 
 	pkt.codec = codec_t::JSON;
 	pkt.definition = definition_t::Any;
-	pkt.block_number = 0;
-	pkt.block_total = 1;
+	pkt.bitrate = 0;
+	pkt.frame_count = 1;
 	pkt.flags = 0;
 	pkt.data = {44,44,44};
 
diff --git a/components/common/cpp/src/timer.cpp b/components/common/cpp/src/timer.cpp
index f13255e7359747406feb5c3d7da3b1ba779c617f..5765eb451ee4ca75a953775b447c4f4163ccc421 100644
--- a/components/common/cpp/src/timer.cpp
+++ b/components/common/cpp/src/timer.cpp
@@ -156,11 +156,14 @@ static void trigger_jobs() {
 		}
 		j.active = true;
 		active_jobs++;
-		ftl::pool.push([&j,ts](int id) {
-			bool doremove = !j.job(ts);
-			j.active = false;
+
+		auto *pj = &j;
+
+		ftl::pool.push([pj,ts](int id) {
+			bool doremove = !pj->job(ts);
+			pj->active = false;
 			active_jobs--;
-			if (doremove) removeJob(j.id);
+			if (doremove) removeJob(pj->id);
 		});
 	}
 }
diff --git a/components/net/cpp/include/ftl/net/dispatcher.hpp b/components/net/cpp/include/ftl/net/dispatcher.hpp
index 0b4d3b34dcb4e1662a43c834abe740319c1f56d2..6f12e0e470a2c627979cec7f9bdfe5b7d6523e59 100644
--- a/components/net/cpp/include/ftl/net/dispatcher.hpp
+++ b/components/net/cpp/include/ftl/net/dispatcher.hpp
@@ -19,6 +19,10 @@
 
 namespace ftl {
 
+namespace net {
+class Peer;
+}
+
 namespace internal {
 	//! \brief Calls a functor with argument provided directly
 	template <typename Functor, typename Arg>
@@ -34,16 +38,28 @@ namespace internal {
 		return func(std::get<I>(params)...);
 	}
 
+	template <typename Functor, typename... Args, std::size_t... I>
+	decltype(auto) call_helper(Functor func, ftl::net::Peer &p, std::tuple<Args...> &&params,
+		                       std::index_sequence<I...>) {
+		return func(p, std::get<I>(params)...);
+	}
+
 	//! \brief Calls a functor with arguments provided as a tuple
 	template <typename Functor, typename... Args>
 	decltype(auto) call(Functor f, std::tuple<Args...> &args) {
 		return call_helper(f, std::forward<std::tuple<Args...>>(args),
 		                   std::index_sequence_for<Args...>{});
 	}
+
+	//! \brief Calls a functor with arguments provided as a tuple
+	template <typename Functor, typename... Args>
+	decltype(auto) call(Functor f, ftl::net::Peer &p, std::tuple<Args...> &args) {
+		return call_helper(f, p, std::forward<std::tuple<Args...>>(args),
+		                   std::index_sequence_for<Args...>{});
+	}
 }
 
 namespace net {
-class Peer;
 
 class Dispatcher {
 	public:
@@ -51,14 +67,17 @@ class Dispatcher {
 	
 	//void dispatch(Peer &, const std::string &msg);
 	void dispatch(Peer &, const msgpack::object &msg);
+
+	// Without peer object =====================================================
 	
 	template <typename F>
 	void bind(std::string const &name, F func,
 		                  ftl::internal::tags::void_result const &,
-		                  ftl::internal::tags::zero_arg const &) {
+		                  ftl::internal::tags::zero_arg const &,
+						  ftl::internal::false_ const &) {
 		enforce_unique_name(name);
 		funcs_.insert(
-		    std::make_pair(name, [func, name](msgpack::object const &args) {
+		    std::make_pair(name, [func, name](ftl::net::Peer &p, msgpack::object const &args) {
 		        enforce_arg_count(name, 0, args.via.array.size);
 		        func();
 		        return std::make_unique<msgpack::object_handle>();
@@ -68,13 +87,14 @@ class Dispatcher {
 	template <typename F>
 	void bind(std::string const &name, F func,
 		                  ftl::internal::tags::void_result const &,
-		                  ftl::internal::tags::nonzero_arg const &) {
+		                  ftl::internal::tags::nonzero_arg const &,
+						  ftl::internal::false_ const &) {
 		using ftl::internal::func_traits;
 		using args_type = typename func_traits<F>::args_type;
 
 		enforce_unique_name(name);
 		funcs_.insert(
-		    std::make_pair(name, [func, name](msgpack::object const &args) {
+		    std::make_pair(name, [func, name](ftl::net::Peer &p, msgpack::object const &args) {
 		        constexpr int args_count = std::tuple_size<args_type>::value;
 		        enforce_arg_count(name, args_count, args.via.array.size);
 		        args_type args_real;
@@ -87,12 +107,13 @@ class Dispatcher {
 	template <typename F>
 	void bind(std::string const &name, F func,
 		                  ftl::internal::tags::nonvoid_result const &,
-		                  ftl::internal::tags::zero_arg const &) {
+		                  ftl::internal::tags::zero_arg const &,
+						  ftl::internal::false_ const &) {
 		using ftl::internal::func_traits;
 
 		enforce_unique_name(name);
 		funcs_.insert(std::make_pair(name, [func,
-		                                    name](msgpack::object const &args) {
+		                                    name](ftl::net::Peer &p, msgpack::object const &args) {
 		    enforce_arg_count(name, 0, args.via.array.size);
 		    auto z = std::make_unique<msgpack::zone>();
 		    auto result = msgpack::object(func(), *z);
@@ -103,13 +124,14 @@ class Dispatcher {
 	template <typename F>
 	void bind(std::string const &name, F func,
 		                  ftl::internal::tags::nonvoid_result const &,
-		                  ftl::internal::tags::nonzero_arg const &) {
+		                  ftl::internal::tags::nonzero_arg const &,
+						  ftl::internal::false_ const &) {
 		using ftl::internal::func_traits;
 		using args_type = typename func_traits<F>::args_type;
 
 		enforce_unique_name(name);
 		funcs_.insert(std::make_pair(name, [func,
-		                                    name](msgpack::object const &args) {
+		                                    name](ftl::net::Peer &p, msgpack::object const &args) {
 		    constexpr int args_count = std::tuple_size<args_type>::value;
 		    enforce_arg_count(name, args_count, args.via.array.size);
 		    args_type args_real;
@@ -120,6 +142,82 @@ class Dispatcher {
 		}));
 	}
 
+	// With peer object ========================================================
+
+	template <typename F>
+	void bind(std::string const &name, F func,
+		                  ftl::internal::tags::void_result const &,
+		                  ftl::internal::tags::zero_arg const &,
+						  ftl::internal::true_ const &) {
+		enforce_unique_name(name);
+		funcs_.insert(
+		    std::make_pair(name, [func, name](ftl::net::Peer &p, msgpack::object const &args) {
+		        enforce_arg_count(name, 0, args.via.array.size);
+		        func(p);
+		        return std::make_unique<msgpack::object_handle>();
+		    }));
+	}
+
+	template <typename F>
+	void bind(std::string const &name, F func,
+		                  ftl::internal::tags::void_result const &,
+		                  ftl::internal::tags::nonzero_arg const &,
+						  ftl::internal::true_ const &) {
+		using ftl::internal::func_traits;
+		using args_type = typename func_traits<F>::args_type;
+
+		enforce_unique_name(name);
+		funcs_.insert(
+		    std::make_pair(name, [func, name](ftl::net::Peer &p, msgpack::object const &args) {
+		        constexpr int args_count = std::tuple_size<args_type>::value;
+		        enforce_arg_count(name, args_count, args.via.array.size);
+		        args_type args_real;
+		        args.convert(args_real);
+		        ftl::internal::call(func, p, args_real);
+		        return std::make_unique<msgpack::object_handle>();
+		    }));
+	}
+
+	template <typename F>
+	void bind(std::string const &name, F func,
+		                  ftl::internal::tags::nonvoid_result const &,
+		                  ftl::internal::tags::zero_arg const &,
+						  ftl::internal::true_ const &) {
+		using ftl::internal::func_traits;
+
+		enforce_unique_name(name);
+		funcs_.insert(std::make_pair(name, [func,
+		                                    name](ftl::net::Peer &p, msgpack::object const &args) {
+		    enforce_arg_count(name, 0, args.via.array.size);
+		    auto z = std::make_unique<msgpack::zone>();
+		    auto result = msgpack::object(func(p), *z);
+		    return std::make_unique<msgpack::object_handle>(result, std::move(z));
+		}));
+	}
+
+	template <typename F>
+	void bind(std::string const &name, F func,
+		                  ftl::internal::tags::nonvoid_result const &,
+		                  ftl::internal::tags::nonzero_arg const &,
+						  ftl::internal::true_ const &) {
+		using ftl::internal::func_traits;
+		using args_type = typename func_traits<F>::args_type;
+
+		enforce_unique_name(name);
+		funcs_.insert(std::make_pair(name, [func,
+		                                    name](ftl::net::Peer &p, msgpack::object const &args) {
+		    constexpr int args_count = std::tuple_size<args_type>::value;
+		    enforce_arg_count(name, args_count, args.via.array.size);
+		    args_type args_real;
+		    args.convert(args_real);
+		    auto z = std::make_unique<msgpack::zone>();
+		    auto result = msgpack::object(ftl::internal::call(func, p, args_real), *z);
+		    return std::make_unique<msgpack::object_handle>(result, std::move(z));
+		}));
+	}
+
+	//==========================================================================
+
 	void unbind(const std::string &name) {
 		auto i = funcs_.find(name);
 		if (i != funcs_.end()) {
@@ -128,9 +226,11 @@ class Dispatcher {
 	}
 	
 	std::vector<std::string> getBindings() const;
+
+	bool isBound(const std::string &name) const;
 	
 	using adaptor_type = std::function<std::unique_ptr<msgpack::object_handle>(
-        msgpack::object const &)>;
+        ftl::net::Peer &, msgpack::object const &)>;
 
     //! \brief This is the type of messages as per the msgpack-rpc spec.
     using call_t = std::tuple<int8_t, uint32_t, std::string, msgpack::object>;
diff --git a/components/net/cpp/include/ftl/net/func_traits.hpp b/components/net/cpp/include/ftl/net/func_traits.hpp
index ae12b335a76d4231c7e497dc0c5440dc7301f5a6..1008f364d514a8652858882bc0883d136dbade9e 100644
--- a/components/net/cpp/include/ftl/net/func_traits.hpp
+++ b/components/net/cpp/include/ftl/net/func_traits.hpp
@@ -9,6 +9,11 @@
 #include <tuple>
 
 namespace ftl {
+
+namespace net {
+class Peer;
+}
+
 namespace internal {
 
 template<typename T>
@@ -58,6 +63,12 @@ struct func_traits<R (C::*)(Args...)> : func_traits<R (*)(Args...)> {};
 template <typename C, typename R, typename... Args>
 struct func_traits<R (C::*)(Args...) const> : func_traits<R (*)(Args...)> {};
 
+template <typename R, typename... Args> struct func_traits<R (*)(ftl::net::Peer &,Args...)> {
+    using result_type = R;
+    using arg_count = std::integral_constant<std::size_t, sizeof...(Args)>;
+    using args_type = std::tuple<typename std::decay<Args>::type...>;
+};
+
 template <typename R, typename... Args> struct func_traits<R (*)(Args...)> {
     using result_type = R;
     using arg_count = std::integral_constant<std::size_t, sizeof...(Args)>;
@@ -80,9 +91,16 @@ template <typename C, typename R, typename... Args>
 struct func_kind_info<R (C::*)(Args...) const>
     : func_kind_info<R (*)(Args...)> {};
 
+template <typename R, typename... Args> struct func_kind_info<R (*)(ftl::net::Peer &,Args...)> {
+    typedef typename tags::arg_count_trait<sizeof...(Args)>::type args_kind;
+    typedef typename tags::result_trait<R>::type result_kind;
+	typedef true_ has_peer;
+};
+
 template <typename R, typename... Args> struct func_kind_info<R (*)(Args...)> {
     typedef typename tags::arg_count_trait<sizeof...(Args)>::type args_kind;
     typedef typename tags::result_trait<R>::type result_kind;
+	typedef false_ has_peer;
 };
 
 template <typename F> using is_zero_arg = is_zero<func_traits<F>::arg_count>;
diff --git a/components/net/cpp/include/ftl/net/peer.hpp b/components/net/cpp/include/ftl/net/peer.hpp
index 976155ac94e323b112306f4fa675512ff3d23aca..4378a25786bdd589157a3aee54cc1584a5a5822b 100644
--- a/components/net/cpp/include/ftl/net/peer.hpp
+++ b/components/net/cpp/include/ftl/net/peer.hpp
@@ -311,7 +311,8 @@ template <typename F>
 void Peer::bind(const std::string &name, F func) {
 	disp_->bind(name, func,
 		typename ftl::internal::func_kind_info<F>::result_kind(),
-	    typename ftl::internal::func_kind_info<F>::args_kind());
+	    typename ftl::internal::func_kind_info<F>::args_kind(),
+		typename ftl::internal::func_kind_info<F>::has_peer());
 }
 
 template <typename R, typename... ARGS>
diff --git a/components/net/cpp/include/ftl/net/universe.hpp b/components/net/cpp/include/ftl/net/universe.hpp
index 29680c601c19ba37ff20771659ef379ce3511631..69d9557397909091abf0e6c924eb05e13566823d 100644
--- a/components/net/cpp/include/ftl/net/universe.hpp
+++ b/components/net/cpp/include/ftl/net/universe.hpp
@@ -110,6 +110,11 @@ class Universe : public ftl::Configurable {
 
 	void unbind(const std::string &name);
 
+	/**
+	 * Check if an RPC name is already bound.
+	 */
+	inline bool isBound(const std::string &name) const { return disp_.isBound(name); }
+
 	/**
 	 * Subscribe a function to a resource. The subscribed function is
 	 * triggered whenever that resource is published to. It is akin to
@@ -259,7 +264,8 @@ void Universe::bind(const std::string &name, F func) {
 	UNIQUE_LOCK(net_mutex_,lk);
 	disp_.bind(name, func,
 		typename ftl::internal::func_kind_info<F>::result_kind(),
-	    typename ftl::internal::func_kind_info<F>::args_kind());
+	    typename ftl::internal::func_kind_info<F>::args_kind(),
+		typename ftl::internal::func_kind_info<F>::has_peer());
 }
 
 /*template <typename F>
diff --git a/components/net/cpp/src/dispatcher.cpp b/components/net/cpp/src/dispatcher.cpp
index 7a5df5091235dc081c0c9e51c199dcca5e7f0f31..eac33f5e134c0ab38194b3726e2036840867eab2 100644
--- a/components/net/cpp/src/dispatcher.cpp
+++ b/components/net/cpp/src/dispatcher.cpp
@@ -76,7 +76,7 @@ void ftl::net::Dispatcher::dispatch_call(Peer &s, const msgpack::object &msg) {
 		if (func) {
 			//DLOG(INFO) << "Found binding for " << name;
 		    try {
-		        auto result = (*func)(args); //->get();
+		        auto result = (*func)(s, args); //->get();
 		        s._sendResponse(id, result->get());
 		        /*response_t res_obj = std::make_tuple(1,id,msgpack::object(),result->get());
 				std::stringstream buf;
@@ -112,6 +112,10 @@ optional<Dispatcher::adaptor_type> ftl::net::Dispatcher::_locateHandler(const st
 	}
 }
 
+bool ftl::net::Dispatcher::isBound(const std::string &name) const {
+	return funcs_.find(name) != funcs_.end();
+}
+
 void ftl::net::Dispatcher::dispatch_notification(Peer &s, msgpack::object const &msg) {
     notification_t the_call;
     msg.convert(the_call);
@@ -128,7 +132,7 @@ void ftl::net::Dispatcher::dispatch_notification(Peer &s, msgpack::object const
 
     if (binding) {
         try {
-            auto result = (*binding)(args);
+            auto result = (*binding)(s, args);
         } catch (const int &e) {
 			LOG(ERROR) << "Exception in bound function";
 			throw &e;
diff --git a/components/net/cpp/test/net_configurable_unit.cpp b/components/net/cpp/test/net_configurable_unit.cpp
index e3fef144490f64ce06375ad8226056afd2f5329f..0b69512f11c0e33f9a83630f76591fec2c353333 100644
--- a/components/net/cpp/test/net_configurable_unit.cpp
+++ b/components/net/cpp/test/net_configurable_unit.cpp
@@ -7,7 +7,7 @@ using ftl::NetConfigurable;
 SCENARIO( "NetConfigurable::set()" ) {
     GIVEN( "valid peer UUID, URI and Master" ) {
         // Set up Master
-        nlohmann::json json = {{"$id", "root"}, {"test", {{"listen", "tcp://localhost:7077"}}}}; // Check what values are needed
+        nlohmann::json json = nlohmann::json{{"$id", "root"}, {"test", {{"listen", "tcp://localhost:7077"}}}}; // Check what values are needed
         ftl::Configurable *root;
         root = new ftl::Configurable(json);
         ftl::net::Universe *net = ftl::config::create<ftl::net::Universe>(root, std::string("test"));
@@ -15,7 +15,7 @@ SCENARIO( "NetConfigurable::set()" ) {
         ftl::ctrl::Master *controller = new ftl::ctrl::Master(root, net);
         
         // Set up a slave, then call getControllers() to get the UUID string
-        nlohmann::json jsonSlave = {{"$id", "slave"}, {"test", {{"peers", {"tcp://localhost:7077"}}}}};
+        nlohmann::json jsonSlave = nlohmann::json{{"$id", "slave"}, {"test", {{"peers", {"tcp://localhost:7077"}}}}};
         ftl::Configurable *rootSlave;
         rootSlave = new ftl::Configurable(jsonSlave);
         ftl::net::Universe *netSlave = ftl::config::create<ftl::net::Universe>(rootSlave, std::string("test"));
@@ -29,7 +29,7 @@ SCENARIO( "NetConfigurable::set()" ) {
 
         ftl::UUID peer = ftl::UUID(controllers[0]["id"].get<std::string>());
         const std::string suri = "slave_test";
-        nlohmann::json jsonTest = {{"$id", "slave_test"}, {"test", {{"peers", {"tcp://localhost:7077"}}}}};
+        nlohmann::json jsonTest = nlohmann::json{{"$id", "slave_test"}, {"test", {{"peers", {"tcp://localhost:7077"}}}}};
         NetConfigurable nc(peer, suri, *controller, jsonTest);
         nc.set("test_value", 5);
         REQUIRE( nc.get<int>("test_value") == 5 );
diff --git a/components/operators/include/ftl/operators/antialiasing.hpp b/components/operators/include/ftl/operators/antialiasing.hpp
index 302631253f0d1aab6e758cf48c0af213bcf3f002..295729bd361e6a17287b845460068a59e4d7555e 100644
--- a/components/operators/include/ftl/operators/antialiasing.hpp
+++ b/components/operators/include/ftl/operators/antialiasing.hpp
@@ -17,7 +17,7 @@ class FXAA : public ftl::operators::Operator {
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
 };
 
diff --git a/components/operators/include/ftl/operators/colours.hpp b/components/operators/include/ftl/operators/colours.hpp
index 5f8ba6cd1d6a7194800491cee47ec079ea304eef..788f7b4f50dec2472453d30ebf4351616de228b2 100644
--- a/components/operators/include/ftl/operators/colours.hpp
+++ b/components/operators/include/ftl/operators/colours.hpp
@@ -13,7 +13,7 @@ class ColourChannels : public ftl::operators::Operator {
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
     private:
     cv::cuda::GpuMat temp_;
diff --git a/components/operators/include/ftl/operators/disparity.hpp b/components/operators/include/ftl/operators/disparity.hpp
index e4ff99d7834ecd8c1f07ee7b950f5dd01479f0e1..f45fa244fc922417e359af7fa22653979b108059 100644
--- a/components/operators/include/ftl/operators/disparity.hpp
+++ b/components/operators/include/ftl/operators/disparity.hpp
@@ -26,7 +26,7 @@ class FixstarsSGM : public ftl::operators::Operator {
 
 	~FixstarsSGM();
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
-	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 	
 	private:
 	bool init();
@@ -52,7 +52,7 @@ class DisparityBilateralFilter : public::ftl::operators::Operator {
 	~DisparityBilateralFilter() {};
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
-	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
 	private:
 	cv::Ptr<cv::cuda::DisparityBilateralFilter> filter_;
@@ -74,7 +74,7 @@ class DisparityToDepth : public ftl::operators::Operator {
 
 	~DisparityToDepth() {};
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
-	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 };
 
 /**
@@ -90,6 +90,7 @@ class DepthChannel : public ftl::operators::Operator {
 	inline Operator::Type type() const override { return Operator::Type::ManyToMany; }
 
     bool apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
     private:
     ftl::operators::Graph *pipe_;
@@ -110,7 +111,7 @@ class OpticalFlowTemporalSmoothing : public ftl::operators::Operator {
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
 	private:
 	bool init();
diff --git a/components/operators/include/ftl/operators/filling.hpp b/components/operators/include/ftl/operators/filling.hpp
index 2825dde5f6840658d7c49dd374792e80952e9391..6de380213ba4fb69bbf966a87db11a7de81f0946 100644
--- a/components/operators/include/ftl/operators/filling.hpp
+++ b/components/operators/include/ftl/operators/filling.hpp
@@ -17,7 +17,7 @@ class ScanFieldFill : public ftl::operators::Operator {
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
 };
 
@@ -28,7 +28,7 @@ class CrossSupportFill : public ftl::operators::Operator {
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
 };
 
diff --git a/components/operators/include/ftl/operators/mask.hpp b/components/operators/include/ftl/operators/mask.hpp
index 579b1f6fe83daf7402041fe139116abe09ee3234..5c542207dbf7216658b2de0d85cf446808db76d3 100644
--- a/components/operators/include/ftl/operators/mask.hpp
+++ b/components/operators/include/ftl/operators/mask.hpp
@@ -19,7 +19,7 @@ class DiscontinuityMask : public ftl::operators::Operator {
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
 };
 
@@ -33,7 +33,7 @@ class CullDiscontinuity : public ftl::operators::Operator {
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
 };
 
diff --git a/components/operators/include/ftl/operators/normals.hpp b/components/operators/include/ftl/operators/normals.hpp
index 5aff09e4da3b5867a77631c81750c42a6c23fdbd..a46d2ff43449deebd2f9a12a87ebebdc2bb3a231 100644
--- a/components/operators/include/ftl/operators/normals.hpp
+++ b/components/operators/include/ftl/operators/normals.hpp
@@ -17,7 +17,7 @@ class Normals : public ftl::operators::Operator {
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
 };
 
@@ -32,7 +32,7 @@ class SmoothNormals : public ftl::operators::Operator {
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
 	private:
 	ftl::cuda::TextureObject<float4> temp_;
diff --git a/components/operators/include/ftl/operators/operator.hpp b/components/operators/include/ftl/operators/operator.hpp
index bc47afef9a976d21e1cb934a4d491a1575f7022d..71d2e939ad5cb90f024a040d3f14a859c35c3936 100644
--- a/components/operators/include/ftl/operators/operator.hpp
+++ b/components/operators/include/ftl/operators/operator.hpp
@@ -37,9 +37,9 @@ class Operator {
 	 */
 	virtual Type type() const =0;
 
-	virtual bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream);
+	virtual bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream);
 	virtual bool apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cudaStream_t stream);
-	virtual bool apply(ftl::rgbd::FrameSet &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *os, cudaStream_t stream);
+	virtual bool apply(ftl::rgbd::FrameSet &in, ftl::rgbd::Frame &out, cudaStream_t stream);
 
 	inline void enable() { enabled_ = true; }
 	inline void disable() { enabled_ = false; }
@@ -92,9 +92,9 @@ class Graph : public ftl::Configurable {
 	template <typename T>
 	ftl::Configurable *append(const std::string &name);
 
-	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream=0);
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream=0);
 	bool apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cudaStream_t stream=0);
-	bool apply(ftl::rgbd::FrameSet &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream=0);
+	bool apply(ftl::rgbd::FrameSet &in, ftl::rgbd::Frame &out, cudaStream_t stream=0);
 
 	private:
 	std::list<ftl::operators::detail::OperatorNode> operators_;
diff --git a/components/operators/include/ftl/operators/opticalflow.hpp b/components/operators/include/ftl/operators/opticalflow.hpp
index 81afc3914b3a3d31975deca14383f319588209d5..e80160631e3cb3270d71da12a72e896231ebd0fd 100644
--- a/components/operators/include/ftl/operators/opticalflow.hpp
+++ b/components/operators/include/ftl/operators/opticalflow.hpp
@@ -17,7 +17,7 @@ class NVOpticalFlow : public ftl::operators::Operator {
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
 	protected:
 	bool init();
diff --git a/components/operators/include/ftl/operators/segmentation.hpp b/components/operators/include/ftl/operators/segmentation.hpp
index f905ce96966d0f68171ee94d209b6d888581005a..d7447615c6d5230f997f9dab857b1ba824b22ce2 100644
--- a/components/operators/include/ftl/operators/segmentation.hpp
+++ b/components/operators/include/ftl/operators/segmentation.hpp
@@ -16,7 +16,7 @@ class CrossSupport : public ftl::operators::Operator {
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
 };
 
@@ -30,7 +30,7 @@ class VisCrossSupport : public ftl::operators::Operator {
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+    bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
 };
 
diff --git a/components/operators/include/ftl/operators/smoothing.hpp b/components/operators/include/ftl/operators/smoothing.hpp
index 6e0c6ff75e382f8a54d99587f545195c70d59e6b..2714af64fa498e6c22bdf4eb7cd242c0d5773e2d 100644
--- a/components/operators/include/ftl/operators/smoothing.hpp
+++ b/components/operators/include/ftl/operators/smoothing.hpp
@@ -19,7 +19,7 @@ class HFSmoother : public ftl::operators::Operator {
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
 	private:
 	cv::cuda::GpuMat temp_;
@@ -40,7 +40,7 @@ class SmoothChannel : public ftl::operators::Operator {
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
 	private:
 	ftl::rgbd::Frame temp_[6];
@@ -58,7 +58,7 @@ class SimpleMLS : public ftl::operators::Operator {
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
 	private:
 };
@@ -74,7 +74,7 @@ class ColourMLS : public ftl::operators::Operator {
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
 	private:
 };
@@ -116,7 +116,7 @@ class AggreMLS : public ftl::operators::Operator {
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
 	private:
 	ftl::cuda::TextureObject<float4> centroid_horiz_;
@@ -138,7 +138,7 @@ class AdaptiveMLS : public ftl::operators::Operator {
 
 	inline Operator::Type type() const override { return Operator::Type::OneToOne; }
 
-	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *src, cudaStream_t stream) override;
+	bool apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) override;
 
 	private:
 };
diff --git a/components/operators/src/antialiasing.cpp b/components/operators/src/antialiasing.cpp
index 575cc26a5183f38dbaa0c861bac5a0cbe70ace18..00bfe3890983f060fe490e003e6c849ab0c47357 100644
--- a/components/operators/src/antialiasing.cpp
+++ b/components/operators/src/antialiasing.cpp
@@ -12,7 +12,7 @@ FXAA::~FXAA() {
 
 }
 
-bool FXAA::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) {
+bool FXAA::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
 	ftl::cuda::fxaa(
 		in.getTexture<uchar4>(Channel::Colour),
 		stream
diff --git a/components/operators/src/clipping.cpp b/components/operators/src/clipping.cpp
index b37abe2c33b55b86b697565d1565b523c073d80f..2d13f65f662973f0dc6c6a47508c97cc8e143a47 100644
--- a/components/operators/src/clipping.cpp
+++ b/components/operators/src/clipping.cpp
@@ -47,13 +47,13 @@ bool ClipScene::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cudaStr
 		
 	for (size_t i=0; i<in.frames.size(); ++i) {	
 		auto &f = in.frames[i];
-		auto *s = in.sources[i];
+		//auto *s = in.sources[i];
 
-		auto pose = MatrixConversion::toCUDA(s->getPose().cast<float>());
+		auto pose = MatrixConversion::toCUDA(f.getPose().cast<float>());
 
 		auto sclip = clip;
 		sclip.origin = sclip.origin * pose;
-		ftl::cuda::clipping(f.createTexture<float>(Channel::Depth), s->parameters(), sclip, stream);
+		ftl::cuda::clipping(f.createTexture<float>(Channel::Depth), f.getLeftCamera(), sclip, stream);
 	}
 
 	return true;
diff --git a/components/operators/src/colours.cpp b/components/operators/src/colours.cpp
index 267bcb8ca7b9dfa7f748fa3d2d3482bae5c5abe0..4d8939f0134eaa018f6b2b51995825b21b522d9e 100644
--- a/components/operators/src/colours.cpp
+++ b/components/operators/src/colours.cpp
@@ -11,7 +11,7 @@ ColourChannels::~ColourChannels() {
 
 }
 
-bool ColourChannels::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) {
+bool ColourChannels::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
 	auto cvstream = cv::cuda::StreamAccessor::wrapStream(stream);
 
 	auto &col = in.get<cv::cuda::GpuMat>(Channel::Colour);
diff --git a/components/operators/src/depth.cpp b/components/operators/src/depth.cpp
index 7122513b1e6e90ea98533972391481dffc2f3e7c..8983f33add79c6a3d6239c4660580a04299aa6da 100644
--- a/components/operators/src/depth.cpp
+++ b/components/operators/src/depth.cpp
@@ -68,9 +68,43 @@ bool DepthChannel::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda
 				cv::cuda::swap(right, rbuf_[i]);
 			}*/
 
-			pipe_->apply(f, f, in.sources[i], stream);
+			pipe_->apply(f, f, stream);
 		}
 	}
 
 	return true;
 }
+
+bool DepthChannel::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
+	auto cvstream = cv::cuda::StreamAccessor::wrapStream(stream);
+
+	//rbuf_.resize(1);
+
+	auto &f = in;
+	if (!f.hasChannel(Channel::Depth) && f.hasChannel(Channel::Right)) {
+		_createPipeline();
+
+		cv::cuda::GpuMat& left = f.get<cv::cuda::GpuMat>(Channel::Left);
+		cv::cuda::GpuMat& right = f.get<cv::cuda::GpuMat>(Channel::Right);
+		cv::cuda::GpuMat& depth = f.create<cv::cuda::GpuMat>(Channel::Depth);
+		depth.create(depth_size_, CV_32FC1);
+
+		if (left.empty() || right.empty()) return false;
+
+		/*if (depth_size_ != left.size()) {
+			auto &col2 = f.create<cv::cuda::GpuMat>(Channel::ColourHighRes);
+			cv::cuda::resize(left, col2, depth_size_, 0.0, 0.0, cv::INTER_CUBIC, cvstream);
+			f.createTexture<uchar4>(Channel::ColourHighRes, true);
+			f.swapChannels(Channel::Colour, Channel::ColourHighRes);
+		}
+
+		if (depth_size_ != right.size()) {
+			cv::cuda::resize(right, rbuf_[i], depth_size_, 0.0, 0.0, cv::INTER_CUBIC, cvstream);
+			cv::cuda::swap(right, rbuf_[i]);
+		}*/
+
+		pipe_->apply(f, f, stream);
+	}
+
+	return true;
+}
diff --git a/components/operators/src/disparity/bilateral_filter.cpp b/components/operators/src/disparity/bilateral_filter.cpp
index 13f2ed1eae60a2de3b3ad8766c395855929bd3fe..0c766596e1307417c23c51602edc72ec954adda3 100644
--- a/components/operators/src/disparity/bilateral_filter.cpp
+++ b/components/operators/src/disparity/bilateral_filter.cpp
@@ -20,7 +20,7 @@ DisparityBilateralFilter::DisparityBilateralFilter(ftl::Configurable* cfg) :
 }
 
 bool DisparityBilateralFilter::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out,
-									 ftl::rgbd::Source *src, cudaStream_t stream) {
+									 cudaStream_t stream) {
 
 	if (!in.hasChannel(Channel::Colour)) {
 		LOG(ERROR) << "Joint Bilateral Filter is missing Colour";
@@ -29,7 +29,7 @@ bool DisparityBilateralFilter::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out
 		// Have depth, so calculate disparity...
 		if (in.hasChannel(Channel::Depth)) {
 			// No disparity, so create it.
-			const auto params = src->parameters();
+			const auto params = in.getLeftCamera();
 			const GpuMat &depth = in.get<GpuMat>(Channel::Depth);
 
 			GpuMat &disp = out.create<GpuMat>(Channel::Disparity);
diff --git a/components/operators/src/disparity/disparity_to_depth.cpp b/components/operators/src/disparity/disparity_to_depth.cpp
index d3cc337728d81b1e73adba27283b935a7878ed04..18a284915f449c34b5b2d9547ed483ef57c9e4dd 100644
--- a/components/operators/src/disparity/disparity_to_depth.cpp
+++ b/components/operators/src/disparity/disparity_to_depth.cpp
@@ -7,14 +7,14 @@ using ftl::codecs::Channel;
 using cv::cuda::GpuMat;
 
 bool DisparityToDepth::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out,
-							 ftl::rgbd::Source *src, cudaStream_t stream) {
+							cudaStream_t stream) {
 	
 	if (!in.hasChannel(Channel::Disparity)) {
 		LOG(ERROR) << "Missing disparity before convert to depth";
 		return false;
 	}
 
-	const auto params = src->parameters();
+	const auto params = in.getLeftCamera();
 	const GpuMat &disp = in.get<GpuMat>(Channel::Disparity);
 
 	GpuMat &depth = out.create<GpuMat>(Channel::Depth);
diff --git a/components/operators/src/disparity/fixstars_sgm.cpp b/components/operators/src/disparity/fixstars_sgm.cpp
index ae15033a2d59f8bf6f235968837b0be4641d04ab..4d605f88a3dbd310a610332f31df217b21f46f3c 100644
--- a/components/operators/src/disparity/fixstars_sgm.cpp
+++ b/components/operators/src/disparity/fixstars_sgm.cpp
@@ -106,7 +106,7 @@ bool FixstarsSGM::updateParameters() {
 		sgm::StereoSGM::Parameters(P1_, P2_, uniqueness_, true));
 }
 
-bool FixstarsSGM::apply(Frame &in, Frame &out, Source *src, cudaStream_t stream) {
+bool FixstarsSGM::apply(Frame &in, Frame &out, cudaStream_t stream) {
 	if (!in.hasChannel(Channel::Left) || !in.hasChannel(Channel::Right)) {
 		LOG(ERROR) << "Fixstars is missing Left or Right channel";
 		return false;
diff --git a/components/operators/src/disparity/optflow_smoothing.cpp b/components/operators/src/disparity/optflow_smoothing.cpp
index efd5d1cc18f41ddd9bff9ee219b4235d48968ef3..71f379c9b26868975a74e371b4e5cc39a4d730fa 100644
--- a/components/operators/src/disparity/optflow_smoothing.cpp
+++ b/components/operators/src/disparity/optflow_smoothing.cpp
@@ -74,7 +74,7 @@ bool OpticalFlowTemporalSmoothing::init() {
 	return true;
 }
 
-bool OpticalFlowTemporalSmoothing::apply(Frame &in, Frame &out, Source *src, cudaStream_t stream) {
+bool OpticalFlowTemporalSmoothing::apply(Frame &in, Frame &out, cudaStream_t stream) {
 	if (!out.hasChannel(channel_) || !in.hasChannel(Channel::Flow)) { return false; }
 
 	auto cvstream = cv::cuda::StreamAccessor::wrapStream(stream);
diff --git a/components/operators/src/filling.cpp b/components/operators/src/filling.cpp
index da7efea6bdf085eb797bc56500f4e783e30f6162..87298cc052cb8d48673a6c2ce13943c35a68caee 100644
--- a/components/operators/src/filling.cpp
+++ b/components/operators/src/filling.cpp
@@ -14,7 +14,7 @@ ScanFieldFill::~ScanFieldFill() {
 
 }
 
-bool ScanFieldFill::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) {
+bool ScanFieldFill::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
 	float thresh = config()->value("threshold", 0.1f);
 
 	ftl::cuda::scan_field_fill(
@@ -22,7 +22,7 @@ bool ScanFieldFill::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd
 		in.createTexture<float>(Channel::Depth),
 		in.createTexture<float>(Channel::Smoothing),
 		thresh,
-		s->parameters(), stream
+		in.getLeftCamera(), stream
 	);
 
 	return true;
@@ -37,7 +37,7 @@ CrossSupportFill::~CrossSupportFill() {
 
 }
 
-bool CrossSupportFill::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) {
+bool CrossSupportFill::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
 
 	/*ftl::cuda::filling_csr(
 		in.createTexture<uchar4>(Channel::Colour2),
diff --git a/components/operators/src/mask.cpp b/components/operators/src/mask.cpp
index e914a5ad8dd8959b840394a543f789fc5252db08..8a8fdaa4b5599ced36cc89d90695a5b8ff20fe81 100644
--- a/components/operators/src/mask.cpp
+++ b/components/operators/src/mask.cpp
@@ -14,7 +14,7 @@ DiscontinuityMask::~DiscontinuityMask() {
 
 }
 
-bool DiscontinuityMask::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) {
+bool DiscontinuityMask::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
 	if (in.hasChannel(Channel::Mask)) return true;
 	
 	int radius = config()->value("radius", 2);
@@ -27,7 +27,7 @@ bool DiscontinuityMask::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::
 		in.createTexture<uchar4>(Channel::Support1),
 		in.createTexture<float>(Channel::Depth),
 		in.get<cv::cuda::GpuMat>(Channel::Depth).size(),
-		s->parameters().minDepth, s->parameters().maxDepth,
+		in.getLeftCamera().minDepth, in.getLeftCamera().maxDepth,
 		radius, threshold, stream
 	);
 
@@ -44,7 +44,8 @@ CullDiscontinuity::~CullDiscontinuity() {
 
 }
 
-bool CullDiscontinuity::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) {
+bool CullDiscontinuity::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
+	out.clearPackets(Channel::Depth);  // Force reset
 	ftl::cuda::cull_discontinuity(
 		in.createTexture<int>(Channel::Mask),
 		out.createTexture<float>(Channel::Depth),
diff --git a/components/operators/src/mvmls.cpp b/components/operators/src/mvmls.cpp
index 8ff3c89dd8ce9f10882e4c5386b4de631f7836b9..3a71dcf4c1350d454751319a38a6ed36c554009c 100644
--- a/components/operators/src/mvmls.cpp
+++ b/components/operators/src/mvmls.cpp
@@ -89,7 +89,7 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda
                 //f1.get<GpuMat>(Channel::Confidence).setTo(cv::Scalar(0.0f), cvstream);
 
                 Eigen::Vector4d d1(0.0, 0.0, 1.0, 0.0);
-                d1 = in.sources[i]->getPose() * d1;
+                d1 = f1.getPose() * d1;
 
                 for (size_t j=0; j<in.frames.size(); ++j) {
                     if (i == j) continue;
@@ -97,16 +97,16 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda
                     //LOG(INFO) << "Running phase1";
 
                     auto &f2 = in.frames[j];
-                    auto s1 = in.sources[i];
-                    auto s2 = in.sources[j];
+                    //auto s1 = in.sources[i];
+                    //auto s2 = in.sources[j];
 
                     // Are cameras facing similar enough direction?
                     Eigen::Vector4d d2(0.0, 0.0, 1.0, 0.0);
-                    d2 = in.sources[j]->getPose() * d2;
+                    d2 = f2.getPose() * d2;
                     // No, so skip this combination
                     if (d1.dot(d2) <= 0.0) continue;
 
-                    auto pose2 = MatrixConversion::toCUDA(s2->getPose().cast<float>().inverse() * s1->getPose().cast<float>());
+                    auto pose2 = MatrixConversion::toCUDA(f2.getPose().cast<float>().inverse() * f1.getPose().cast<float>());
 
                     //auto transform = pose2 * pose1;
 
@@ -121,8 +121,8 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda
                         f1.getTexture<float>(Channel::Confidence),
                         f1.getTexture<int>(Channel::Mask),
                         pose2,
-                        s1->parameters(),
-                        s2->parameters(),
+                        f1.getLeftCamera(),
+                        f2.getLeftCamera(),
                         params,
                         win,
                         stream
@@ -200,7 +200,7 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda
         // But don't do the final move step.
         for (size_t i=0; i<in.frames.size(); ++i) {
             auto &f = in.frames[i];
-            auto *s = in.sources[i];
+            //auto *s = in.sources[i];
 
             // Clear data
             cv::cuda::GpuMat data(contributions_[i].height(), contributions_[i].width(), CV_32F, contributions_[i].pixelPitch());
@@ -224,7 +224,7 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda
                 thresh,
                 col_smooth,
                 radius,
-                s->parameters(),
+                f.getLeftCamera(),
                 stream
             );
 
@@ -239,7 +239,7 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda
                 thresh,
                 col_smooth,
                 radius,
-                s->parameters(),
+                f.getLeftCamera(),
                 stream
             );
         }
@@ -254,7 +254,7 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda
                 //f1.get<GpuMat>(Channel::Confidence).setTo(cv::Scalar(0.0f), cvstream);
 
                 Eigen::Vector4d d1(0.0, 0.0, 1.0, 0.0);
-                d1 = in.sources[i]->getPose() * d1;
+                d1 = f1.getPose() * d1;
 
                 for (size_t j=0; j<in.frames.size(); ++j) {
                     if (i == j) continue;
@@ -262,19 +262,19 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda
                     //LOG(INFO) << "Running phase1";
 
                     auto &f2 = in.frames[j];
-                    auto s1 = in.sources[i];
-                    auto s2 = in.sources[j];
+                    //auto s1 = in.sources[i];
+                    //auto s2 = in.sources[j];
 
                     // Are cameras facing similar enough direction?
                     Eigen::Vector4d d2(0.0, 0.0, 1.0, 0.0);
-                    d2 = in.sources[j]->getPose() * d2;
+                    d2 = f2.getPose() * d2;
                     // No, so skip this combination
                     if (d1.dot(d2) <= 0.0) continue;
 
-                    auto pose1 = MatrixConversion::toCUDA(s1->getPose().cast<float>());
-					auto pose1_inv = MatrixConversion::toCUDA(s1->getPose().cast<float>().inverse());
-					auto pose2 = MatrixConversion::toCUDA(s2->getPose().cast<float>().inverse());
-					auto pose2_inv = MatrixConversion::toCUDA(s2->getPose().cast<float>());
+                    auto pose1 = MatrixConversion::toCUDA(f1.getPose().cast<float>());
+					auto pose1_inv = MatrixConversion::toCUDA(f1.getPose().cast<float>().inverse());
+					auto pose2 = MatrixConversion::toCUDA(f2.getPose().cast<float>().inverse());
+					auto pose2_inv = MatrixConversion::toCUDA(f2.getPose().cast<float>());
 
 					auto transform = pose2 * pose1;
 
@@ -289,8 +289,8 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda
                         //contributions_[j],
                         //f1.getTexture<short2>(Channel::Screen),
 						transform,
-						s1->parameters(),
-						s2->parameters(),
+						f1.getLeftCamera(),
+						f2.getLeftCamera(),
                         stream
                     );
 
@@ -303,7 +303,7 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda
         // Normalise aggregations and move the points
         for (size_t i=0; i<in.frames.size(); ++i) {
             auto &f = in.frames[i];
-            auto *s = in.sources[i];
+            //auto *s = in.sources[i];
             auto size = f.get<GpuMat>(Channel::Depth).size();
 
             /*if (do_corr) {
@@ -320,7 +320,7 @@ bool MultiViewMLS::apply(ftl::rgbd::FrameSet &in, ftl::rgbd::FrameSet &out, cuda
                 centroid_vert_[i],
                 f.getTexture<float>(Channel::Depth),
                 f.createTexture<float>(Channel::Depth2, ftl::rgbd::Format<float>(size)),
-                s->parameters(),
+                f.getLeftCamera(),
                 stream
             );
 
diff --git a/components/operators/src/normals.cpp b/components/operators/src/normals.cpp
index 65fcef37bac5b55f16e9f1d07ac7e12f1c58cfad..8e2ab7d3f52b0b3a69d895c211f7bd90780c76fc 100644
--- a/components/operators/src/normals.cpp
+++ b/components/operators/src/normals.cpp
@@ -15,7 +15,7 @@ Normals::~Normals() {
 
 }
 
-bool Normals::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) {
+bool Normals::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
 	if (!in.hasChannel(Channel::Depth)) {
 		LOG(ERROR) << "Missing depth channel in Normals operator";
 		return false;
@@ -29,7 +29,7 @@ bool Normals::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Sour
 	ftl::cuda::normals(
 		out.createTexture<float4>(Channel::Normals, ftl::rgbd::Format<float4>(in.get<cv::cuda::GpuMat>(Channel::Depth).size())),
 		in.createTexture<float>(Channel::Depth),
-		s->parameters(), stream
+		in.getLeftCamera(), stream
 	);
 
 	return true;
@@ -44,7 +44,7 @@ SmoothNormals::~SmoothNormals() {
 
 }
 
-bool SmoothNormals::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) {
+bool SmoothNormals::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
     float smoothing = config()->value("normal_smoothing", 0.02f);
     int radius = max(0, min(config()->value("radius",1), 5));
 
@@ -67,9 +67,9 @@ bool SmoothNormals::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd
 		temp_,
 		in.createTexture<float>(Channel::Depth),
 		radius, smoothing,
-		s->parameters(),
-		MatrixConversion::toCUDA(s->getPose().cast<float>().inverse()).getFloat3x3(),
-		MatrixConversion::toCUDA(s->getPose().cast<float>()).getFloat3x3(),
+		in.getLeftCamera(),
+		MatrixConversion::toCUDA(in.getPose().cast<float>().inverse()).getFloat3x3(),
+		MatrixConversion::toCUDA(in.getPose().cast<float>()).getFloat3x3(),
 		stream
 	);
 
diff --git a/components/operators/src/nvopticalflow.cpp b/components/operators/src/nvopticalflow.cpp
index a8b6d406bc7421d48d0cfb15de40d0eb48cafebe..6ce98d757d076496077a13b5b0dc64f8e51560cb 100644
--- a/components/operators/src/nvopticalflow.cpp
+++ b/components/operators/src/nvopticalflow.cpp
@@ -28,7 +28,7 @@ bool NVOpticalFlow::init() {
 	return true;
 }
 
-bool NVOpticalFlow::apply(Frame &in, Frame &out, Source *src, cudaStream_t stream) {
+bool NVOpticalFlow::apply(Frame &in, Frame &out, cudaStream_t stream) {
 	if (!in.hasChannel(channel_in_)) { return false; }
 
 	if (in.get<GpuMat>(channel_in_).size() != size_) {
diff --git a/components/operators/src/operator.cpp b/components/operators/src/operator.cpp
index 6b796267d6f34bfde24e71d0a13afdd262c1ac45..ff1ebc6693e06ab7f5c6563470ef681c135e5deb 100644
--- a/components/operators/src/operator.cpp
+++ b/components/operators/src/operator.cpp
@@ -16,15 +16,18 @@ Operator::Operator(ftl::Configurable *config) : config_(config) {
 
 Operator::~Operator() {}
 
-bool Operator::apply(Frame &in, Frame &out, Source *s, cudaStream_t stream) {
+bool Operator::apply(Frame &in, Frame &out, cudaStream_t stream) {
+	LOG(ERROR) << "Operation application to frame not supported";
 	return false;
 }
 
 bool Operator::apply(FrameSet &in, FrameSet &out, cudaStream_t stream) {
+	LOG(ERROR) << "Operation application to frameset not supported";
 	return false;
 }
 
-bool Operator::apply(FrameSet &in, Frame &out, Source *os, cudaStream_t stream) {
+bool Operator::apply(FrameSet &in, Frame &out, cudaStream_t stream) {
+	LOG(ERROR) << "Operation application as a reduction not supported";
 	return false;
 }
 
@@ -55,7 +58,7 @@ bool Graph::apply(FrameSet &in, FrameSet &out, cudaStream_t stream) {
 			//while (i.instances.size() < in.frames.size()) {
 				//i.instances.push_back(i.maker->make());
 			//}
-			if (in.frames.size() > 1) {
+			if (in.frames.size() > 1 && i.instances.size() < 2) {
 				i.instances.push_back(i.maker->make());
 			}
 
@@ -64,7 +67,7 @@ bool Graph::apply(FrameSet &in, FrameSet &out, cudaStream_t stream) {
 
 				if (instance->enabled()) {
 					try {
-						if (!instance->apply(in.frames[j], out.frames[j], in.sources[j], stream_actual)) return false;
+						if (!instance->apply(in.frames[j], out.frames[j], stream_actual)) return false;
 					} catch (const std::exception &e) {
 						LOG(ERROR) << "Operator exception: " << e.what();
 					}
@@ -91,9 +94,11 @@ bool Graph::apply(FrameSet &in, FrameSet &out, cudaStream_t stream) {
 	return true;
 }
 
-bool Graph::apply(Frame &in, Frame &out, Source *s, cudaStream_t stream) {
+bool Graph::apply(Frame &in, Frame &out, cudaStream_t stream) {
 	if (!value("enabled", true)) return false;
 
+	auto stream_actual = (stream == 0) ? stream_ : stream;
+
 	for (auto &i : operators_) {
 		// Make sure there are enough instances
 		if (i.instances.size() < 1) {
@@ -103,10 +108,15 @@ bool Graph::apply(Frame &in, Frame &out, Source *s, cudaStream_t stream) {
 		auto *instance = i.instances[0];
 
 		if (instance->enabled()) {
-			if (!instance->apply(in, out, s, stream)) return false;
+			if (!instance->apply(in, out, stream_actual)) return false;
 		}
 	}
 
+	if (stream == 0) {
+		cudaStreamSynchronize(stream_actual);
+		cudaSafeCall( cudaGetLastError() );
+	}
+
 	return true;
 }
 
diff --git a/components/operators/src/segmentation.cpp b/components/operators/src/segmentation.cpp
index 005c436d2dd25f158a92cd511825a13c785b4977..32de62112d856cb186c6db21bf3534d0733246ce 100644
--- a/components/operators/src/segmentation.cpp
+++ b/components/operators/src/segmentation.cpp
@@ -13,7 +13,7 @@ CrossSupport::~CrossSupport() {
 
 }
 
-bool CrossSupport::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) {
+bool CrossSupport::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
 	bool use_mask = config()->value("discon_support", false);
 
 
@@ -51,7 +51,7 @@ VisCrossSupport::~VisCrossSupport() {
 
 }
 
-bool VisCrossSupport::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) {
+bool VisCrossSupport::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
 	bool show_depth = false;
 	if (in.hasChannel(Channel::Support2) && config()->value("show_depth_support", false)) {
 		show_depth = true;
diff --git a/components/operators/src/smoothing.cpp b/components/operators/src/smoothing.cpp
index 2eafe7977a827cd0a1e90017f749fd1245ce1b68..dd403d4b8508d66daf522a02714e8bcb4c8e09df 100644
--- a/components/operators/src/smoothing.cpp
+++ b/components/operators/src/smoothing.cpp
@@ -21,7 +21,7 @@ HFSmoother::~HFSmoother() {
 
 }
 
-bool HFSmoother::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) {
+bool HFSmoother::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
     float var_thresh = config()->value("variance_threshold", 0.0002f);
     int levels = max(0, min(config()->value("levels",0), 4));
     int iters = config()->value("iterations",5);
@@ -34,7 +34,7 @@ bool HFSmoother::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::S
             in.createTexture<float>(Channel::Energy, ftl::rgbd::Format<float>(in.get<cv::cuda::GpuMat>(Channel::Depth).size())),
             in.createTexture<float>(Channel::Smoothing, ftl::rgbd::Format<float>(in.get<cv::cuda::GpuMat>(Channel::Depth).size())),
             var_thresh,
-            s->parameters(), stream
+            in.getLeftCamera(), stream
         );
     }
 
@@ -72,13 +72,13 @@ SmoothChannel::~SmoothChannel() {
 
 }
 
-bool SmoothChannel::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) {
+bool SmoothChannel::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
 	int radius = config()->value("radius", 3);
 	float threshold = config()->value("threshold", 30.0f);
 	int iters = max(0, min(6, config()->value("levels", 4)));
 
-	int width = s->parameters().width;
-	int height = s->parameters().height;
+	int width = in.getLeftCamera().width;
+	int height = in.getLeftCamera().height;
 	float scale = 1.0f;
 
 	// Clear to max smoothing
@@ -89,7 +89,7 @@ bool SmoothChannel::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd
 		in.createTexture<uchar4>(Channel::Colour),
 		//in.createTexture<float>(Channel::Depth),
 		out.createTexture<float>(Channel::Smoothing),
-		s->parameters(),
+		in.getLeftCamera(),
 		threshold,
 		scale,
 		radius,
@@ -101,7 +101,7 @@ bool SmoothChannel::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd
 		height /= 2;
 		scale *= 2.0f;
 
-		ftl::rgbd::Camera scaledCam = s->parameters().scaled(width, height);
+		ftl::rgbd::Camera scaledCam = in.getLeftCamera().scaled(width, height);
 
 		// Downscale images for next pass
 		cv::cuda::resize(in.get<GpuMat>(Channel::Colour), temp_[i].create<GpuMat>(Channel::Colour), cv::Size(width, height), 0.0, 0.0, cv::INTER_LINEAR);
@@ -134,7 +134,7 @@ SimpleMLS::~SimpleMLS() {
 
 }
 
-bool SimpleMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) {
+bool SimpleMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
 	float thresh = config()->value("mls_threshold", 0.04f);
 	int iters = config()->value("mls_iterations", 1);
 	int radius = config()->value("mls_radius",2);
@@ -158,7 +158,7 @@ bool SimpleMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::So
 			in.createTexture<float>(Channel::Depth2, ftl::rgbd::Format<float>(in.get<cv::cuda::GpuMat>(Channel::Depth).size())),
 			thresh,
 			radius,
-			s->parameters(),
+			in.getLeftCamera(),
 			stream
 		);
 
@@ -179,7 +179,7 @@ ColourMLS::~ColourMLS() {
 
 }
 
-bool ColourMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) {
+bool ColourMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
 	float thresh = config()->value("mls_threshold", 0.4f);
 	float col_smooth = config()->value("mls_colour_smoothing", 30.0f);
 	int iters = config()->value("mls_iterations", 3);
@@ -204,7 +204,7 @@ bool ColourMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::So
 				thresh,
 				col_smooth,
 				radius,
-				s->parameters(),
+				in.getLeftCamera(),
 				stream
 			);
 		} else {
@@ -218,7 +218,7 @@ bool ColourMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::So
 				thresh,
 				col_smooth,
 				filling,
-				s->parameters(),
+				in.getLeftCamera(),
 				stream
 			);
 		}
@@ -241,7 +241,7 @@ AggreMLS::~AggreMLS() {
 
 }
 
-bool AggreMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) {
+bool AggreMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
 	float thresh = config()->value("mls_threshold", 0.4f);
 	float col_smooth = config()->value("mls_colour_smoothing", 30.0f);
 	int iters = config()->value("mls_iterations", 3);
@@ -277,7 +277,7 @@ bool AggreMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Sou
 				thresh,
 				col_smooth,
 				radius,
-				s->parameters(),
+				in.getLeftCamera(),
 				stream
 			);
 
@@ -292,7 +292,7 @@ bool AggreMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Sou
 				thresh,
 				col_smooth,
 				radius,
-				s->parameters(),
+				in.getLeftCamera(),
 				stream
 			);
 
@@ -301,7 +301,7 @@ bool AggreMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Sou
 				centroid_vert_,
 				in.createTexture<float>(Channel::Depth),
 				in.createTexture<float>(Channel::Depth2, ftl::rgbd::Format<float>(size)),
-				s->parameters(),
+				in.getLeftCamera(),
 				stream
 			);
 
@@ -319,7 +319,7 @@ bool AggreMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Sou
 				thresh,
 				col_smooth,
 				false,
-				s->parameters(),
+				in.getLeftCamera(),
 				stream
 			);
 
@@ -342,7 +342,7 @@ AdaptiveMLS::~AdaptiveMLS() {
 
 }
 
-bool AdaptiveMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) {
+bool AdaptiveMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, cudaStream_t stream) {
 	int iters = config()->value("mls_iterations", 1);
 	int radius = config()->value("mls_radius",2);
 
@@ -360,7 +360,7 @@ bool AdaptiveMLS::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::
 			in.createTexture<float>(Channel::Depth2, ftl::rgbd::Format<float>(in.get<cv::cuda::GpuMat>(Channel::Depth).size())),
 			in.createTexture<float>(Channel::Smoothing),
 			radius,
-			s->parameters(),
+			in.getLeftCamera(),
 			stream
 		);
 
diff --git a/components/renderers/cpp/CMakeLists.txt b/components/renderers/cpp/CMakeLists.txt
index 112650feee5853b71c8c3cd7f1e265c3e2c0e564..0d292b397c07d5406ab68e9d8084fb27cc9129b7 100644
--- a/components/renderers/cpp/CMakeLists.txt
+++ b/components/renderers/cpp/CMakeLists.txt
@@ -1,5 +1,4 @@
 add_library(ftlrender
-	src/splatter.cpp
 	src/splatter.cu
 	src/points.cu
 	src/normals.cu
diff --git a/components/renderers/cpp/include/ftl/render/renderer.hpp b/components/renderers/cpp/include/ftl/render/renderer.hpp
index 605fa27d182fec5c6463faff397af83b25b43d85..3a36655147fc58a34c2b5605f28f949d93238fab 100644
--- a/components/renderers/cpp/include/ftl/render/renderer.hpp
+++ b/components/renderers/cpp/include/ftl/render/renderer.hpp
@@ -2,7 +2,7 @@
 #define _FTL_RENDER_RENDERER_HPP_
 
 #include <ftl/configurable.hpp>
-#include <ftl/rgbd/virtual.hpp>
+#include <ftl/rgbd/frameset.hpp>
 #include <ftl/cuda_common.hpp>
 
 namespace ftl {
@@ -22,11 +22,10 @@ class Renderer : public ftl::Configurable {
     virtual ~Renderer() {};
 
     /**
-     * Generate a single virtual camera frame. The frame takes its pose from
-     * the virtual camera object passed, and writes the result into the
-     * virtual camera.
+     * Generate a single virtual frame. The frame takes its pose and calibration
+	 * from the output frame pose and calibration channels.
      */
-    virtual bool render(ftl::rgbd::VirtualSource *, ftl::rgbd::Frame &, const Eigen::Matrix4d &)=0;
+    virtual bool render(ftl::rgbd::FrameSet &, ftl::rgbd::Frame &, ftl::codecs::Channel, const Eigen::Matrix4d &)=0;
 };
 
 }
diff --git a/components/renderers/cpp/include/ftl/render/tri_render.hpp b/components/renderers/cpp/include/ftl/render/tri_render.hpp
index d15ba9c73116e4dfeb3a15ca006ae88329e4cc80..c709e818f97bc9284e75836c13209513be666f53 100644
--- a/components/renderers/cpp/include/ftl/render/tri_render.hpp
+++ b/components/renderers/cpp/include/ftl/render/tri_render.hpp
@@ -16,10 +16,10 @@ namespace render {
  */
 class Triangular : public ftl::render::Renderer {
 	public:
-	explicit Triangular(nlohmann::json &config, ftl::rgbd::FrameSet *fs);
+	explicit Triangular(nlohmann::json &config);
 	~Triangular();
 
-	bool render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, const Eigen::Matrix4d &t) override;
+	bool render(ftl::rgbd::FrameSet &in, ftl::rgbd::Frame &out, ftl::codecs::Channel, const Eigen::Matrix4d &t) override;
 	//void setOutputDevice(int);
 
 	protected:
@@ -55,7 +55,16 @@ class Triangular : public ftl::render::Renderer {
 	void __reprojectChannel(ftl::rgbd::Frame &, ftl::codecs::Channel in, ftl::codecs::Channel out, const Eigen::Matrix4d &t, cudaStream_t);
 	void _reprojectChannel(ftl::rgbd::Frame &, ftl::codecs::Channel in, ftl::codecs::Channel out, const Eigen::Matrix4d &t, cudaStream_t);
 	void _dibr(ftl::rgbd::Frame &, const Eigen::Matrix4d &t, cudaStream_t);
-	void _mesh(ftl::rgbd::Frame &, ftl::rgbd::Source *, const Eigen::Matrix4d &t, cudaStream_t);
+	void _mesh(ftl::rgbd::Frame &, const Eigen::Matrix4d &t, cudaStream_t);
+	void _preprocessColours();
+	void _updateParameters(ftl::rgbd::Frame &out);
+	void _allocateChannels(ftl::rgbd::Frame &out);
+	void _postprocessColours(ftl::rgbd::Frame &out);
+
+	void _renderNormals(ftl::rgbd::Frame &out);
+	void _renderDensity(ftl::rgbd::Frame &out, const Eigen::Matrix4d &t);
+	void _renderRight(ftl::rgbd::Frame &out, const Eigen::Matrix4d &t);
+	void _renderSecond(ftl::rgbd::Frame &out, ftl::codecs::Channel chan, const Eigen::Matrix4d &t);
 
 	bool _alreadySeen() const { return last_frame_ == scene_->timestamp; }
 };
diff --git a/components/renderers/cpp/src/splatter.cpp b/components/renderers/cpp/src/splatter.cpp
index fad38ba82a1c3925069afd982170761f8d57cd78..731efec404dc24a972f290c61d8fc0c6d2ba34c7 100644
--- a/components/renderers/cpp/src/splatter.cpp
+++ b/components/renderers/cpp/src/splatter.cpp
@@ -204,7 +204,7 @@ void Splatter::_dibr(cudaStream_t stream) {
 
 	for (size_t i=0; i < scene_->frames.size(); ++i) {
 		auto &f = scene_->frames[i];
-		auto *s = scene_->sources[i];
+		//auto *s = scene_->sources[i];
 
 		if (f.empty(Channel::Depth + Channel::Colour)) {
 			LOG(ERROR) << "Missing required channel";
@@ -355,7 +355,7 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cons
 	temp_.createTexture<float4>(Channel::Normals);
 	for (int i=0; i<scene_->frames.size(); ++i) {
 		auto &f = scene_->frames[i];
-		auto s = scene_->sources[i];
+		//auto s = scene_->sources[i];
 
 		if (f.hasChannel(Channel::Mask)) {
 			if (show_discon) {
@@ -371,8 +371,8 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cons
 			//LOG(INFO) << "Creating points... " << s->parameters().width;
 			
 			auto &t = f.createTexture<float4>(Channel::Points, Format<float4>(f.get<GpuMat>(Channel::Colour).size()));
-			auto pose = MatrixConversion::toCUDA(s->getPose().cast<float>()); //.inverse());
-			ftl::cuda::point_cloud(t, f.createTexture<float>(Channel::Depth), s->parameters(), pose, 0, stream_);
+			auto pose = MatrixConversion::toCUDA(f.getPose().cast<float>()); //.inverse());
+			ftl::cuda::point_cloud(t, f.createTexture<float>(Channel::Depth), f.getLeftCamera(), pose, 0, stream_);
 
 			//LOG(INFO) << "POINTS Added";
 		}
@@ -383,7 +383,7 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cons
 		}
 
 		if (!f.hasChannel(Channel::Normals)) {
-			Eigen::Matrix4f matrix =  s->getPose().cast<float>().transpose();
+			Eigen::Matrix4f matrix =  f.getPose().cast<float>().transpose();
 			auto pose = MatrixConversion::toCUDA(matrix);
 
 			auto &g = f.get<GpuMat>(Channel::Colour);
@@ -391,10 +391,10 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cons
 				temp_.getTexture<float4>(Channel::Normals),
 				f.getTexture<float4>(Channel::Points),
 				1, 0.02f,
-				s->parameters(), pose.getFloat3x3(), stream_);
+				f.getLeftCamera(), pose.getFloat3x3(), stream_);
 
 			if (norm_filter_ > -0.1f) {
-				ftl::cuda::normal_filter(f.getTexture<float4>(Channel::Normals), f.getTexture<float4>(Channel::Points), s->parameters(), pose, norm_filter_, stream_);
+				ftl::cuda::normal_filter(f.getTexture<float4>(Channel::Normals), f.getTexture<float4>(Channel::Points), f.getLeftCamera(), pose, norm_filter_, stream_);
 			}
 		}
 	}
diff --git a/components/renderers/cpp/src/tri_render.cpp b/components/renderers/cpp/src/tri_render.cpp
index 6df15bbd19cd63df9a8cdd2a5d4edbb1fc20329b..659bcc8a99c929840506604d1a2dddb4b5dbc58f 100644
--- a/components/renderers/cpp/src/tri_render.cpp
+++ b/components/renderers/cpp/src/tri_render.cpp
@@ -67,7 +67,7 @@ static uchar4 parseCUDAColour(const std::string &colour) {
 	return make_uchar4(0,0,0,0);
 }
 
-Triangular::Triangular(nlohmann::json &config, ftl::rgbd::FrameSet *fs) : ftl::render::Renderer(config), scene_(fs) {
+Triangular::Triangular(nlohmann::json &config) : ftl::render::Renderer(config), scene_(nullptr) {
 	if (config["clipping"].is_object()) {
 		auto &c = config["clipping"];
 		float rx = c.value("pitch", 0.0f);
@@ -219,7 +219,7 @@ void Triangular::__reprojectChannel(ftl::rgbd::Frame &output, ftl::codecs::Chann
 
 	for (size_t i=0; i < scene_->frames.size(); ++i) {
 		auto &f = scene_->frames[i];
-		auto *s = scene_->sources[i];
+		//auto *s = scene_->sources[i];
 		
 		/*if (f.get<GpuMat>(in).type() == CV_8UC3) {
 			// Convert to 4 channel colour
@@ -229,8 +229,8 @@ void Triangular::__reprojectChannel(ftl::rgbd::Frame &output, ftl::codecs::Chann
 			cv::cuda::cvtColor(tmp,col, cv::COLOR_BGR2BGRA);
 		}*/
 
-		auto transform = MatrixConversion::toCUDA(s->getPose().cast<float>().inverse() * t.cast<float>().inverse()) * params_.m_viewMatrixInverse;
-		auto transformR = MatrixConversion::toCUDA(s->getPose().cast<float>().inverse()).getFloat3x3();
+		auto transform = MatrixConversion::toCUDA(f.getPose().cast<float>().inverse() * t.cast<float>().inverse()) * params_.m_viewMatrixInverse;
+		auto transformR = MatrixConversion::toCUDA(f.getPose().cast<float>().inverse()).getFloat3x3();
 
 		if (mesh_) {
 			ftl::cuda::reproject(
@@ -241,7 +241,7 @@ void Triangular::__reprojectChannel(ftl::rgbd::Frame &output, ftl::codecs::Chann
 				temp_.createTexture<typename AccumSelector<T>::type>(AccumSelector<T>::channel),
 				temp_.getTexture<float>(Channel::Contribution),
 				params_,
-				s->parameters(),
+				f.getLeftCamera(),
 				transform, transformR, stream
 			);
 		} else {
@@ -253,7 +253,7 @@ void Triangular::__reprojectChannel(ftl::rgbd::Frame &output, ftl::codecs::Chann
 				temp_.createTexture<typename AccumSelector<T>::type>(AccumSelector<T>::channel),
 				temp_.getTexture<float>(Channel::Contribution),
 				params_,
-				s->parameters(),
+				f.getLeftCamera(),
 				transform, stream
 			);
 		}
@@ -295,20 +295,20 @@ void Triangular::_dibr(ftl::rgbd::Frame &out, const Eigen::Matrix4d &t, cudaStre
 
 	for (size_t i=0; i < scene_->frames.size(); ++i) {
 		auto &f = scene_->frames[i];
-		auto *s = scene_->sources[i];
+		//auto *s = scene_->sources[i];
 
 		if (f.empty(Channel::Depth + Channel::Colour)) {
 			LOG(ERROR) << "Missing required channel";
 			continue;
 		}
 
-		auto transform = params_.m_viewMatrix * MatrixConversion::toCUDA(t.cast<float>() * s->getPose().cast<float>());
+		auto transform = params_.m_viewMatrix * MatrixConversion::toCUDA(t.cast<float>() * f.getPose().cast<float>());
 
 		ftl::cuda::dibr_merge(
 			f.createTexture<float>(Channel::Depth),
 			temp_.createTexture<int>(Channel::Depth2),
 			transform,
-			s->parameters(),
+			f.getLeftCamera(),
 			params_, stream
 		);
 	}
@@ -317,7 +317,7 @@ void Triangular::_dibr(ftl::rgbd::Frame &out, const Eigen::Matrix4d &t, cudaStre
 	temp_.get<GpuMat>(Channel::Depth2).convertTo(out.get<GpuMat>(Channel::Depth), CV_32F, 1.0f / 100000.0f, cvstream);
 }
 
-void Triangular::_mesh(ftl::rgbd::Frame &out, ftl::rgbd::Source *src, const Eigen::Matrix4d &t, cudaStream_t stream) {
+void Triangular::_mesh(ftl::rgbd::Frame &out, const Eigen::Matrix4d &t, cudaStream_t stream) {
 	cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream);
 
 	bool do_blend = value("mesh_blend", true);
@@ -332,21 +332,21 @@ void Triangular::_mesh(ftl::rgbd::Frame &out, ftl::rgbd::Source *src, const Eige
 	// For each source depth map
 	for (size_t i=0; i < scene_->frames.size(); ++i) {
 		auto &f = scene_->frames[i];
-		auto *s = scene_->sources[i];
+		//auto *s = scene_->sources[i];
 
 		if (f.empty(Channel::Depth + Channel::Colour)) {
 			LOG(ERROR) << "Missing required channel";
 			continue;
 		}
 
-		auto pose = MatrixConversion::toCUDA(t.cast<float>() * s->getPose().cast<float>());
+		auto pose = MatrixConversion::toCUDA(t.cast<float>() * f.getPose().cast<float>());
 
 		// Calculate and save virtual view screen position of each source pixel
 		ftl::cuda::screen_coord(
 			f.createTexture<float>(Channel::Depth),
 			f.createTexture<float>(Channel::Depth2, Format<float>(f.get<GpuMat>(Channel::Depth).size())),
 			f.createTexture<short2>(Channel::Screen, Format<short2>(f.get<GpuMat>(Channel::Depth).size())),
-			params_, pose, s->parameters(), stream
+			params_, pose, f.getLeftCamera(), stream
 		);
 
 		// Must reset depth channel if blending
@@ -465,72 +465,53 @@ static cv::Scalar HSVtoRGB(int H, double S, double V) {
 	return cv::Scalar((Bs + m) * 255, (Gs + m) * 255, (Rs + m) * 255, 0);
 }
 
-bool Triangular::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, const Eigen::Matrix4d &t) {
-	SHARED_LOCK(scene_->mtx, lk);
-	if (!src->isReady()) return false;
-
-	//scene_->upload(Channel::Colour + Channel::Depth, stream_);
-
-	const auto &camera = src->parameters();
+void Triangular::_allocateChannels(ftl::rgbd::Frame &out) {
+	const auto &camera = out.getLeftCamera();
 	cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream_);
-	//cudaSafeCall(cudaSetDevice(scene_->getCUDADevice()));
 
-	// Parameters object to pass to CUDA describing the camera
-	SplatParams &params = params_;
-	params.triangle_limit = value("triangle_limit", 200);
-	params.depthThreshold = value("depth_threshold", 0.04f);
-	params.m_flags = 0;
-	//if () params.m_flags |= ftl::render::kShowDisconMask;
-	if (value("normal_weight_colours", true)) params.m_flags |= ftl::render::kNormalWeightColours;
-	params.m_viewMatrix = MatrixConversion::toCUDA(src->getPose().cast<float>().inverse());
-	params.m_viewMatrixInverse = MatrixConversion::toCUDA(src->getPose().cast<float>());
-	params.camera = camera;
-
-	// Create all the required channels
-	
+	// Only do this if not already done, allows for multiple render passes with
+	// different framesets.
 	if (!out.hasChannel(Channel::Depth)) {
 		out.create<GpuMat>(Channel::Depth, Format<float>(camera.width, camera.height));
 		out.create<GpuMat>(Channel::Colour, Format<uchar4>(camera.width, camera.height));
 		out.createTexture<uchar4>(Channel::Colour, true);  // Force interpolated colour
-
 		out.get<GpuMat>(Channel::Depth).setTo(cv::Scalar(1000.0f), cvstream);
-
-		if (env_image_.empty() || !value("environment_enabled", false)) {
-			out.get<GpuMat>(Channel::Colour).setTo(background_, cvstream);
-		} else {
-			auto pose = params.m_viewMatrixInverse.getFloat3x3();
-			ftl::cuda::equirectangular_reproject(
-				env_tex_,
-				out.createTexture<uchar4>(Channel::Colour, true),
-				camera, pose, stream_);
-		}
 	}
 
-	if (scene_->frames.size() == 0) return false;
-	auto &g = scene_->frames[0].get<GpuMat>(Channel::Colour);
-
 	temp_.create<GpuMat>(Channel::Colour, Format<float4>(camera.width, camera.height));
 	temp_.create<GpuMat>(Channel::Contribution, Format<float>(camera.width, camera.height));
 	temp_.create<GpuMat>(Channel::Depth, Format<int>(camera.width, camera.height));
 	temp_.create<GpuMat>(Channel::Depth2, Format<int>(camera.width, camera.height));
-	temp_.create<GpuMat>(Channel::Normals, Format<float4>(camera.width, camera.height)); //g.cols, g.rows));
+	temp_.create<GpuMat>(Channel::Normals, Format<float4>(camera.width, camera.height));
+	temp_.createTexture<int>(Channel::Depth);
+}
 
-	//LOG(INFO) << "Render ready: " << camera.width << "," << camera.height;
+void Triangular::_updateParameters(ftl::rgbd::Frame &out) {
+	const auto &camera = out.getLeftCamera();
+
+	// Parameters object to pass to CUDA describing the camera
+	params_.triangle_limit = value("triangle_limit", 200);
+	params_.depthThreshold = value("depth_threshold", 0.04f);
+	params_.m_flags = 0;
+	if (value("normal_weight_colours", true)) params_.m_flags |= ftl::render::kNormalWeightColours;
+	params_.m_viewMatrix = MatrixConversion::toCUDA(out.getPose().cast<float>().inverse());
+	params_.m_viewMatrixInverse = MatrixConversion::toCUDA(out.getPose().cast<float>());
+	params_.camera = camera;
+}
 
+void Triangular::_preprocessColours() {
 	bool show_discon = value("show_discontinuity_mask", false);
 	bool show_fill = value("show_filled", false);
 	bool colour_sources = value("colour_sources", false);
 
-	temp_.createTexture<int>(Channel::Depth);
-	//temp_.get<GpuMat>(Channel::Normals).setTo(cv::Scalar(0.0f,0.0f,0.0f,0.0f), cvstream);
+	cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream_);
 
 	// Display mask values or otherwise alter colour image
 	for (int i=0; i<scene_->frames.size(); ++i) {
 		auto &f = scene_->frames[i];
-		auto s = scene_->sources[i];
 
 		if (colour_sources) {
-			auto colour = HSVtoRGB(360 / scene_->frames.size() * i, 0.6, 0.85); //(i == 0) ? cv::Scalar(255,0,0,0) : cv::Scalar(0,255,0,0);
+			auto colour = HSVtoRGB(360 / scene_->frames.size() * i, 0.6, 0.85);
 			f.get<GpuMat>(Channel::Colour).setTo(colour, cvstream);
 		}
 
@@ -543,87 +524,14 @@ bool Triangular::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, co
 			}
 		}
 
-		/*// Needs to create points channel first?
-		if (!f.hasChannel(Channel::Points)) {
-			//LOG(INFO) << "Creating points... " << s->parameters().width;
-			
-			auto &t = f.createTexture<float4>(Channel::Points, Format<float4>(f.get<GpuMat>(Channel::Colour).size()));
-			auto pose = MatrixConversion::toCUDA(s->getPose().cast<float>()); //.inverse());
-			ftl::cuda::point_cloud(t, f.createTexture<float>(Channel::Depth), s->parameters(), pose, 0, stream_);
-
-			//LOG(INFO) << "POINTS Added";
-		}
-
-		// Clip first?
-		if (clipping_) {
-			ftl::cuda::clipping(f.createTexture<float4>(Channel::Points), clip_, stream_);
-		}
-
-		if (!f.hasChannel(Channel::Normals)) {
-			Eigen::Matrix4f matrix =  s->getPose().cast<float>().transpose();
-			auto pose = MatrixConversion::toCUDA(matrix);
-
-			auto &g = f.get<GpuMat>(Channel::Colour);
-			ftl::cuda::normals(f.createTexture<float4>(Channel::Normals, Format<float4>(g.cols, g.rows)),
-				temp_.getTexture<float4>(Channel::Normals),
-				f.getTexture<float4>(Channel::Points),
-				1, 0.02f,
-				s->parameters(), pose.getFloat3x3(), stream_);
-
-			if (norm_filter_ > -0.1f) {
-				ftl::cuda::normal_filter(f.getTexture<float4>(Channel::Normals), f.getTexture<float4>(Channel::Points), s->parameters(), pose, norm_filter_, stream_);
-			}
-		}*/
-	}
-
-	Channel chan = src->getChannel();
-
-	int aligned_source = value("aligned_source",-1);
-	if (aligned_source >= 0 && aligned_source < scene_->frames.size()) {
-		// Can only send at originally received frame rate due to reuse of
-		// encodings that can't be sent twice.
-		if (_alreadySeen()) {
-			out.reset();
-			return false;
-		}
-
-		// FIXME: Output may not be same resolution as source!
-		cudaSafeCall(cudaStreamSynchronize(stream_));
-		scene_->frames[aligned_source].copyTo(Channel::Depth + Channel::Colour + Channel::Smoothing + Channel::Confidence, out);
-
-		if (chan == Channel::ColourNormals) {
-			// Convert normal to single float value
-			out.create<GpuMat>(Channel::ColourNormals, Format<uchar4>(out.get<GpuMat>(Channel::Colour).size())).setTo(cv::Scalar(0,0,0,0), cvstream);
-			ftl::cuda::normal_visualise(scene_->frames[aligned_source].getTexture<float4>(Channel::Normals), out.createTexture<uchar4>(Channel::ColourNormals),
-					light_pos_,
-					light_diffuse_,
-					light_ambient_, stream_);
-
-			// Put in output as single float
-			//cv::cuda::swap(temp_.get<GpuMat>(Channel::Colour), out.create<GpuMat>(Channel::Normals));
-			//out.resetTexture(Channel::Normals);
-		}
-
-		last_frame_ = scene_->timestamp;
-		return true;
+		// Force interpolated colour
+		f.createTexture<uchar4>(Channel::Colour, true);
 	}
+}
 
-	// Create and render triangles for depth
-	if (mesh_) {
-		_mesh(out, src, t, stream_);
-	} else {
-		_dibr(out, t, stream_);
-	}
-
-	// Reprojection of colours onto surface
-	auto main_channel = (scene_->frames[0].hasChannel(Channel::ColourHighRes)) ? Channel::ColourHighRes : Channel::Colour;
-	//if (scene_->frames[0].hasChannel(Channel::ColourHighRes)) {
-	//	LOG(INFO) << "HAVE HIGH RES: " << scene_->frames[0].get<GpuMat>(Channel::ColourHighRes).rows;
-	//}
-	_renderChannel(out, main_channel, Channel::Colour, t, stream_);
-
+void Triangular::_postprocessColours(ftl::rgbd::Frame &out) {
 	if (value("cool_effect", false)) {
-		auto pose = params.m_viewMatrixInverse.getFloat3x3();
+		auto pose = params_.m_viewMatrixInverse.getFloat3x3();
 		auto col = parseCUDAColour(value("cool_effect_colour", std::string("#2222ff")));
 
 		ftl::cuda::cool_blue(
@@ -640,76 +548,121 @@ bool Triangular::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, co
 			out.getTexture<uchar4>(Channel::Colour),
 			temp_.getTexture<float>(Channel::Contribution),
 			make_uchar4(255,0,0,0),
-			camera,
+			params_.camera,
 			stream_
 		);
 	}
+}
+
+void Triangular::_renderNormals(ftl::rgbd::Frame &out) {
+	cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream_);
+
+	// Visualise normals to RGBA
+	out.create<GpuMat>(Channel::ColourNormals, Format<uchar4>(params_.camera.width, params_.camera.height)).setTo(cv::Scalar(0,0,0,0), cvstream);
+
+	ftl::cuda::normal_visualise(out.getTexture<float4>(Channel::Normals), out.createTexture<uchar4>(Channel::ColourNormals),
+			light_pos_,
+			light_diffuse_,
+			light_ambient_, stream_);
+}
+
+void Triangular::_renderDensity(ftl::rgbd::Frame &out, const Eigen::Matrix4d &t) {
+	cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream_);
+	out.create<GpuMat>(Channel::Density, Format<float>(params_.camera.width, params_.camera.height));
+	out.get<GpuMat>(Channel::Density).setTo(cv::Scalar(0.0f), cvstream);
+	_renderChannel(out, Channel::Depth, Channel::Density, t, stream_);
+}
+
+void Triangular::_renderRight(ftl::rgbd::Frame &out, const Eigen::Matrix4d &t) {
+	cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream_);
+
+	float baseline = params_.camera.baseline;
+
+	Eigen::Matrix4f transform = Eigen::Matrix4f::Identity();
+	transform(0, 3) = baseline;
+	Eigen::Matrix4f matrix = out.getPose().cast<float>() * transform.inverse();
+	
+	params_.m_viewMatrix = MatrixConversion::toCUDA(matrix.inverse());
+	params_.m_viewMatrixInverse = MatrixConversion::toCUDA(matrix);
+	params_.camera = out.getRightCamera();
 	
-	if (chan == Channel::Depth)
-	{
-		// Just convert int depth to float depth
-		//temp_.get<GpuMat>(Channel::Depth2).convertTo(out.get<GpuMat>(Channel::Depth), CV_32F, 1.0f / 100000.0f, cvstream);
-	} else if (chan == Channel::ColourNormals) {
-		// Visualise normals to RGBA
-		out.create<GpuMat>(Channel::ColourNormals, Format<uchar4>(camera.width, camera.height)).setTo(cv::Scalar(0,0,0,0), cvstream);
-
-		ftl::cuda::normal_visualise(out.getTexture<float4>(Channel::Normals), out.createTexture<uchar4>(Channel::ColourNormals),
-				light_pos_,
-				light_diffuse_,
-				light_ambient_, stream_);
-
-		//accum_.swapTo(Channels(Channel::Normals), out);
-		//cv::cuda::swap(accum_.get<GpuMat>(Channel::Normals), out.get<GpuMat>(Channel::Normals));
-		//out.resetTexture(Channel::Normals);
-		//accum_.resetTexture(Channel::Normals);
+	out.create<GpuMat>(Channel::Right, Format<uchar4>(params_.camera.width, params_.camera.height));
+	out.get<GpuMat>(Channel::Right).setTo(background_, cvstream);
+	out.createTexture<uchar4>(Channel::Right, true);
+
+	// Need to re-dibr due to pose change
+	if (mesh_) {
+		_mesh(out, t, stream_);
+	} else {
+		_dibr(out, t, stream_);
 	}
-	//else if (chan == Channel::Contribution)
-	//{
-	//	cv::cuda::swap(temp_.get<GpuMat>(Channel::Contribution), out.create<GpuMat>(Channel::Contribution));
-	//}
-	else if (chan == Channel::Density) {
-		out.create<GpuMat>(chan, Format<float>(camera.width, camera.height));
+	_renderChannel(out, Channel::Left, Channel::Right, t, stream_);
+}
+
+void Triangular::_renderSecond(ftl::rgbd::Frame &out, ftl::codecs::Channel chan, const Eigen::Matrix4d &t) {
+	cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream_);
+
+	if (ftl::codecs::isFloatChannel(chan)) {
+		out.create<GpuMat>(chan, Format<float>(params_.camera.width, params_.camera.height));
 		out.get<GpuMat>(chan).setTo(cv::Scalar(0.0f), cvstream);
-		_renderChannel(out, Channel::Depth, Channel::Density, t, stream_);
+	} else {
+		out.create<GpuMat>(chan, Format<uchar4>(params_.camera.width, params_.camera.height));
+		out.get<GpuMat>(chan).setTo(background_, cvstream);
 	}
-	else if (chan == Channel::Right)
-	{
-		float baseline = camera.baseline;
-		
-		//Eigen::Translation3f translation(baseline, 0.0f, 0.0f);
-		//Eigen::Affine3f transform(translation);
-		//Eigen::Matrix4f matrix = transform.matrix() * src->getPose().cast<float>();
+	_renderChannel(out, chan, chan, t, stream_);
+}
 
-		Eigen::Matrix4f transform = Eigen::Matrix4f::Identity();
-		transform(0, 3) = baseline;
-		Eigen::Matrix4f matrix = src->getPose().cast<float>() * transform.inverse();
-		
-		params.m_viewMatrix = MatrixConversion::toCUDA(matrix.inverse());
-		params.m_viewMatrixInverse = MatrixConversion::toCUDA(matrix);
+bool Triangular::render(ftl::rgbd::FrameSet &in, ftl::rgbd::Frame &out, Channel chan, const Eigen::Matrix4d &t) {
+	scene_ = &in;
+	if (scene_->frames.size() == 0) return false;
 
-		params.camera = src->parameters(Channel::Right);
-		
-		out.create<GpuMat>(Channel::Right, Format<uchar4>(camera.width, camera.height));
-		out.get<GpuMat>(Channel::Right).setTo(background_, cvstream);
-		out.createTexture<uchar4>(Channel::Right, true);
+	const auto &camera = out.getLeftCamera();
+	cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream_);
+	//cudaSafeCall(cudaSetDevice(scene_->getCUDADevice()));
 
-		// Need to re-dibr due to pose change
-		if (mesh_) {
-			_mesh(out, src, t, stream_);
-		} else {
-			_dibr(out, t, stream_);
-		}
-		_renderChannel(out, Channel::Left, Channel::Right, t, stream_);
+	_updateParameters(out);
 
-	} else if (chan != Channel::None) {
-		if (ftl::codecs::isFloatChannel(chan)) {
-			out.create<GpuMat>(chan, Format<float>(camera.width, camera.height));
-			out.get<GpuMat>(chan).setTo(cv::Scalar(0.0f), cvstream);
-		} else {
-			out.create<GpuMat>(chan, Format<uchar4>(camera.width, camera.height));
-			out.get<GpuMat>(chan).setTo(background_, cvstream);
-		}
-		_renderChannel(out, chan, chan, t, stream_);
+	// Create all the required channels
+	_allocateChannels(out);
+
+	// Apply a colour background
+	if (env_image_.empty() || !value("environment_enabled", false)) {
+		out.get<GpuMat>(Channel::Colour).setTo(background_, cvstream);
+	} else {
+		auto pose = params_.m_viewMatrixInverse.getFloat3x3();
+		ftl::cuda::equirectangular_reproject(
+			env_tex_,
+			out.createTexture<uchar4>(Channel::Colour, true),
+			camera, pose, stream_);
+	}
+
+	// Render source specific debug info into colour channels
+	_preprocessColours();
+
+	if (mesh_) {
+		// Render depth channel using triangles
+		_mesh(out, t, stream_);
+	} else {
+		// Render depth channel as a point cloud
+		_dibr(out, t, stream_);
+	}
+
+	// Reprojection of colours onto surface
+	auto main_channel = (scene_->frames[0].hasChannel(Channel::ColourHighRes)) ? Channel::ColourHighRes : Channel::Colour;
+	_renderChannel(out, main_channel, Channel::Colour, t, stream_);
+
+	// Debug colour info relating to the rendering process
+	_postprocessColours(out);
+
+	// Support rendering of a second channel without redoing all the work
+	switch(chan) {
+	case Channel::None			:
+	case Channel::Left			:
+	case Channel::Depth			: break;
+	case Channel::ColourNormals	: _renderNormals(out); break;
+	case Channel::Density		: _renderDensity(out, t); break;
+	case Channel::Right			: _renderRight(out, t); break;
+	default						: _renderSecond(out, chan, t);
 	}
 
 	cudaSafeCall(cudaStreamSynchronize(stream_));
diff --git a/components/rgbd-sources/CMakeLists.txt b/components/rgbd-sources/CMakeLists.txt
index b22623bcef38c04fd5b2d7535728bee1616c5fb6..405712691cd9cea781fd8cab5e41fdfccef4e05c 100644
--- a/components/rgbd-sources/CMakeLists.txt
+++ b/components/rgbd-sources/CMakeLists.txt
@@ -5,15 +5,15 @@ set(RGBDSRC
 	src/frame.cpp
 	src/frameset.cpp
 	src/sources/stereovideo/stereovideo.cpp
-	src/sources/net/net.cpp
-	src/streamer.cpp
+	#src/sources/net/net.cpp
+	#src/streamer.cpp
 	src/colour.cpp
 	src/group.cpp
 	src/cb_segmentation.cpp
 	src/abr.cpp
-	src/sources/virtual/virtual.cpp
-	src/sources/ftlfile/file_source.cpp
-	src/sources/ftlfile/player.cpp
+	#src/sources/virtual/virtual.cpp
+	#src/sources/ftlfile/file_source.cpp
+	#src/sources/ftlfile/player.cpp
 )
 
 if (HAVE_REALSENSE)
diff --git a/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp b/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp
index cc5aeec50c1acc61ad4ed04acdbbaf03b2c4ba08..bb162c925586d177ed37465a778555254000d631 100644
--- a/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/detail/source.hpp
@@ -28,7 +28,7 @@ class Source {
 	friend class ftl::rgbd::Source;
 
 	public:
-	explicit Source(ftl::rgbd::Source *host) : capabilities_(0), host_(host), params_({0}), timestamp_(0) { }
+	explicit Source(ftl::rgbd::Source *host) : capabilities_(0), host_(host), params_(state_.getLeft()), timestamp_(0) { }
 	virtual ~Source() {}
 
 	/**
@@ -54,17 +54,18 @@ class Source {
 	virtual void swap() {}
 
 	virtual bool isReady() { return false; };
-	virtual void setPose(const Eigen::Matrix4d &pose) { };
+	virtual void setPose(const Eigen::Matrix4d &pose) { state_.setPose(pose); };
 
 	virtual Camera parameters(ftl::codecs::Channel) { return params_; };
 
 	protected:
+	ftl::rgbd::FrameState state_;
 	capability_t capabilities_;
 	ftl::rgbd::Source *host_;
-	ftl::rgbd::Camera params_;
+	ftl::rgbd::Camera &params_;
 	ftl::rgbd::Frame frame_;
 	int64_t timestamp_;
-	//Eigen::Matrix4f pose_;
+	//Eigen::Matrix4d &pose_;
 };
 
 }	
diff --git a/components/rgbd-sources/include/ftl/rgbd/frame.hpp b/components/rgbd-sources/include/ftl/rgbd/frame.hpp
index 109eb008a9e51ed5e8bdf77d68b3c59f6daccb64..85b97326b263ccd354924253f1a2652ea22959f9 100644
--- a/components/rgbd-sources/include/ftl/rgbd/frame.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/frame.hpp
@@ -10,6 +10,7 @@
 
 #include <ftl/codecs/channels.hpp>
 #include <ftl/rgbd/format.hpp>
+#include <ftl/rgbd/camera.hpp>
 #include <ftl/codecs/bitrates.hpp>
 #include <ftl/codecs/packet.hpp>
 
@@ -19,6 +20,8 @@
 #include <array>
 #include <list>
 
+#include <Eigen/Eigen>
+
 namespace ftl {
 namespace rgbd {
 
@@ -28,15 +31,130 @@ namespace rgbd {
 class Frame;
 class Source;
 
+/**
+ * Represent state that is persistent across frames. Such state may or may not
+ * change from one frame to the next so a record of what has changed must be
+ * kept. Changing state should be done at origin and not in the frame. State
+ * that is marked as changed will then be send into a stream and the changed
+ * status will be cleared, allowing data to only be sent/saved when actual
+ * changes occur.
+ */
+class FrameState {
+	public:
+	FrameState();
+	FrameState(FrameState &);
+	FrameState(FrameState &&);
+
+	/**
+	 * Update the pose and mark as changed.
+	 */
+	void setPose(const Eigen::Matrix4d &pose);
+
+	/**
+	 * Update the left camera intrinsics and mark as changed.
+	 */
+	void setLeft(const ftl::rgbd::Camera &p);
+
+	/**
+	 * Update the right camera intrinsics and mark as changed.
+	 */
+	void setRight(const ftl::rgbd::Camera &p);
+
+	/**
+	 * Get the current camera pose.
+	 */
+	inline const Eigen::Matrix4d &getPose() const { return pose_; }
+
+	/**
+	 * Get the left camera intrinsics.
+	 */
+	inline const ftl::rgbd::Camera &getLeft() const { return camera_left_; }
+
+	/**
+	 * Get the right camera intrinsics.
+	 */
+	inline const ftl::rgbd::Camera &getRight() const { return camera_right_; }
+
+	/**
+	 * Get a modifiable pose reference that does not change the changed status.
+	 * @attention Should only be used internally.
+	 * @todo Make private eventually.
+	 */
+	inline Eigen::Matrix4d &getPose() { return pose_; }
+
+	/**
+	 * Get a modifiable left camera intrinsics reference that does not change
+	 * the changed status. Modifications made using this will not be propagated.
+	 * @attention Should only be used internally.
+	 * @todo Make private eventually.
+	 */
+	inline ftl::rgbd::Camera &getLeft() { return camera_left_; }
+
+	/**
+	 * Get a modifiable right camera intrinsics reference that does not change
+	 * the changed status. Modifications made using this will not be propagated.
+	 * @attention Should only be used internally.
+	 * @todo Make private eventually.
+	 */
+	inline ftl::rgbd::Camera &getRight() { return camera_right_; }
+
+	/**
+	 * Get a named config property.
+	 */
+	template <typename T>
+	std::optional<T> get(const std::string &name) {
+		try {
+			return config_[name].get<T>();
+		} catch (...) {
+			return {};
+		}
+	}
+
+	/**
+	 * Set a named config property. Also makes state as changed to be resent.
+	 */
+	template <typename T>
+	void set(const std::string &name, T value) {
+		config_[name] = value;
+		changed_ += ftl::codecs::Channel::Configuration;
+	}
+
+	inline const nlohmann::json &getConfig() const { return config_; }
+
+	inline nlohmann::json &getConfig() { return config_; }
+
+	/**
+	 * Check if pose of intrinsics have been modified and not yet forwarded.
+	 * Once forwarded through a pipeline / stream the changed status is cleared.
+	 */
+	inline bool hasChanged(ftl::codecs::Channel c) const { return changed_.has(c); }
+
+	/**
+	 * Copy assignment will clear the changed status of the original.
+	 */
+	FrameState &operator=(FrameState &);
+
+	FrameState &operator=(FrameState &&);
+
+	/**
+	 * Clear the changed status to unchanged.
+	 */
+	inline void clear() { changed_.clear(); }
+
+	private:
+	Eigen::Matrix4d pose_;
+	ftl::rgbd::Camera camera_left_;
+	ftl::rgbd::Camera camera_right_;
+	nlohmann::json config_;
+	ftl::codecs::Channels<64> changed_;  // Have the state channels changed?
+};
+
 /**
  * Manage a set of image channels corresponding to a single camera frame.
  */
 class Frame {
 public:
-	Frame() : src_(nullptr) {}
-	explicit Frame(ftl::rgbd::Source *src) : src_(src) {}
-
-	inline ftl::rgbd::Source *source() const { return src_; }
+	Frame() : origin_(nullptr) {}
 
 	// Prevent frame copy, instead use a move.
 	//Frame(const Frame &)=delete;
@@ -44,27 +162,27 @@ public:
 
 	void download(ftl::codecs::Channel c, cv::cuda::Stream stream);
 	void upload(ftl::codecs::Channel c, cv::cuda::Stream stream);
-	void download(ftl::codecs::Channels c, cv::cuda::Stream stream);
-	void upload(ftl::codecs::Channels c, cv::cuda::Stream stream);
+	void download(ftl::codecs::Channels<0> c, cv::cuda::Stream stream);
+	void upload(ftl::codecs::Channels<0> c, cv::cuda::Stream stream);
 
 	inline void download(ftl::codecs::Channel c, cudaStream_t stream=0) { download(c, cv::cuda::StreamAccessor::wrapStream(stream)); };
 	inline void upload(ftl::codecs::Channel c, cudaStream_t stream=0) { upload(c, cv::cuda::StreamAccessor::wrapStream(stream)); };
-	inline void download(ftl::codecs::Channels c, cudaStream_t stream=0) { download(c, cv::cuda::StreamAccessor::wrapStream(stream)); };
-	inline void upload(ftl::codecs::Channels c, cudaStream_t stream=0) { upload(c, cv::cuda::StreamAccessor::wrapStream(stream)); };
+	inline void download(const ftl::codecs::Channels<0> &c, cudaStream_t stream=0) { download(c, cv::cuda::StreamAccessor::wrapStream(stream)); };
+	inline void upload(const ftl::codecs::Channels<0> &c, cudaStream_t stream=0) { upload(c, cv::cuda::StreamAccessor::wrapStream(stream)); };
 
 	/**
 	 * Perform a buffer swap of the selected channels. This is intended to be
 	 * a copy from `this` to the passed frame object but by buffer swap
 	 * instead of memory copy, meaning `this` may become invalid afterwards.
 	 */
-	void swapTo(ftl::codecs::Channels, Frame &);
+	void swapTo(ftl::codecs::Channels<0>, Frame &);
 
 	void swapChannels(ftl::codecs::Channel, ftl::codecs::Channel);
 
 	/**
 	 * Does a host or device memory copy into the given frame.
 	 */
-	void copyTo(ftl::codecs::Channels, Frame &);
+	void copyTo(ftl::codecs::Channels<0>, Frame &);
 
 	/**
 	 * Create a channel with a given format. This will discard any existing
@@ -100,8 +218,22 @@ public:
 	 */
 	void pushPacket(ftl::codecs::Channel c, ftl::codecs::Packet &pkt);
 
+	/**
+	 * Obtain a list of any existing encodings for this channel.
+	 */
 	const std::list<ftl::codecs::Packet> &getPackets(ftl::codecs::Channel c) const;
 
+	/**
+	 * Clear any existing encoded packets. Used when the channel data is
+	 * modified and the encodings are therefore out-of-date.
+	 */
+	void clearPackets(ftl::codecs::Channel c);
+
+	/**
+	 * Packets from multiple frames are merged together in sequence. An example
+	 * case is if a frame gets dropped but the original encoding is inter-frame
+	 * and hence still requires the dropped frames encoding data.
+	 */
 	void mergeEncoding(ftl::rgbd::Frame &f);
 
 	void resetTexture(ftl::codecs::Channel c);
@@ -116,21 +248,34 @@ public:
 	 */
 	void resetFull();
 
-	bool empty(ftl::codecs::Channels c);
+	/**
+	 * Check if any specified channels are empty or missing.
+	 */
+	bool empty(ftl::codecs::Channels<0> c);
 
+	/**
+	 * Check if a specific channel is missing or has no memory allocated.
+	 */
 	inline bool empty(ftl::codecs::Channel c) {
 		auto &m = _get(c);
 		return !hasChannel(c) || (m.host.empty() && m.gpu.empty());
 	}
 
 	/**
-	 * Is there valid data in channel (either host or gpu).
+	 * Is there valid data in channel (either host or gpu). This does not
+	 * verify that any memory or data exists for the channel.
 	 */
 	inline bool hasChannel(ftl::codecs::Channel channel) const {
-		return channels_.has(channel);
+		int c = static_cast<int>(channel);
+		if (c >= 64 && c <= 68) return true;
+		else if (c >= 32) return false;
+		else return channels_.has(channel);
 	}
 
-	inline ftl::codecs::Channels getChannels() const { return channels_; }
+	/**
+	 * Obtain a mask of all available channels in the frame.
+	 */
+	inline ftl::codecs::Channels<0> getChannels() const { return channels_; }
 
 	/**
 	 * Is the channel data currently located on GPU. This also returns false if
@@ -148,6 +293,15 @@ public:
 		return channels_.has(channel) && !gpu_.has(channel);
 	}
 
+	/**
+	 * Does this frame have new data for a channel. This is compared with a
+	 * previous frame and always returns true for image data. It may return
+	 * false for persistent state data (calibration, pose etc).
+	 */
+	inline bool hasChanged(ftl::codecs::Channel c) const {
+		return (static_cast<int>(c) < 32) ? true : state_.hasChanged(c);
+	}
+
 	/**
 	 * Method to get reference to the channel content.
 	 * @param	Channel type
@@ -170,9 +324,82 @@ public:
 	 */
 	template <typename T> T& get(ftl::codecs::Channel channel);
 
+	/**
+	 * Get an existing CUDA texture object.
+	 */
 	template <typename T> const ftl::cuda::TextureObject<T> &getTexture(ftl::codecs::Channel) const;
+
+	/**
+	 * Get an existing CUDA texture object.
+	 */
 	template <typename T> ftl::cuda::TextureObject<T> &getTexture(ftl::codecs::Channel);
 
+	/**
+	 * Wrapper accessor function to get frame pose.
+	 */
+	const Eigen::Matrix4d &getPose() const;
+
+	/**
+	 * Change the pose of the origin state and mark as changed.
+	 */
+	void setPose(const Eigen::Matrix4d &pose);
+
+	/**
+	 * Wrapper to access left camera intrinsics channel.
+	 */
+	const ftl::rgbd::Camera &getLeftCamera() const;
+
+	/**
+	 * Wrapper to access right camera intrinsics channel.
+	 */
+	const ftl::rgbd::Camera &getRightCamera() const;
+
+	/**
+	 * Change left camera intrinsics in the origin state. This should send
+	 * the changed parameters in reverse through a stream.
+	 */
+	void setLeftCamera(const ftl::rgbd::Camera &c);
+
+	/**
+	 * Change right camera intrinsics in the origin state. This should send
+	 * the changed parameters in reverse through a stream.
+	 */
+	void setRightCamera(const ftl::rgbd::Camera &c);
+
+	/**
+	 * Dump the current frame config object to a json string.
+	 */
+	std::string getConfigString() const;
+
+	/**
+	 * Wrapper to access a config property. If the property does not exist or
+	 * is not of the requested type then the returned optional is false.
+	 */
+	template <typename T>
+	std::optional<T> get(const std::string &name) { return state_.get<T>(name); }
+
+	/**
+	 * Modify a config property. This does not modify the origin config so
+	 * will not get transmitted over the stream.
+	 * @todo Modify origin to send backwards over a stream.
+	 */
+	template <typename T>
+	void set(const std::string &name, T value) { state_.set(name, value); }
+
+	/**
+	 * Set the persistent state for the frame. This can only be done after
+	 * construction or a reset. Multiple calls to this otherwise will throw
+	 * an exception. The pointer must remain valid for the life of the frame.
+	 */
+	void setOrigin(ftl::rgbd::FrameState *state);
+
+	/**
+	 * Get the original frame state object. This can be a nullptr in some rare
+	 * cases. When wishing to change state (pose, calibration etc) then those
+	 * changes must be done on this origin, either directly or via wrappers.
+	 */
+	FrameState *origin() const { return origin_; }
+
 private:
 	struct ChannelData {
 		ftl::cuda::TextureObjectBase tex;
@@ -181,13 +408,16 @@ private:
 		std::list<ftl::codecs::Packet> encoded;
 	};
 
-	std::array<ChannelData, ftl::codecs::Channels::kMax> data_;
+	std::array<ChannelData, ftl::codecs::Channels<0>::kMax> data_;
 
-	ftl::codecs::Channels channels_;	// Does it have a channel
-	ftl::codecs::Channels gpu_;		// Is the channel on a GPU
+	ftl::codecs::Channels<0> channels_;	// Does it have a channel
+	ftl::codecs::Channels<0> gpu_;		// Is the channel on a GPU
 
-	ftl::rgbd::Source *src_;
+	// Persistent state
+	FrameState state_;
+	FrameState *origin_;
 
+	/* Lookup internal state for a given channel. */
 	inline ChannelData &_get(ftl::codecs::Channel c) { return data_[static_cast<unsigned int>(c)]; }
 	inline const ChannelData &_get(ftl::codecs::Channel c) const { return data_[static_cast<unsigned int>(c)]; }
 };
@@ -199,6 +429,9 @@ template<> const cv::cuda::GpuMat& Frame::get(ftl::codecs::Channel channel) cons
 template<> cv::Mat& Frame::get(ftl::codecs::Channel channel);
 template<> cv::cuda::GpuMat& Frame::get(ftl::codecs::Channel channel);
 
+//template<> const Eigen::Matrix4d &Frame::get(ftl::codecs::Channel channel) const;
+template<> const ftl::rgbd::Camera &Frame::get(ftl::codecs::Channel channel) const;
+
 template <> cv::Mat &Frame::create(ftl::codecs::Channel c, const ftl::rgbd::FormatBase &);
 template <> cv::cuda::GpuMat &Frame::create(ftl::codecs::Channel c, const ftl::rgbd::FormatBase &);
 template <> cv::Mat &Frame::create(ftl::codecs::Channel c);
diff --git a/components/rgbd-sources/include/ftl/rgbd/frameset.hpp b/components/rgbd-sources/include/ftl/rgbd/frameset.hpp
index ee1322b73f6ea147aaad5b986791dc9127342db5..b85e99be987c31fdcb5f3116372efcb631d5442d 100644
--- a/components/rgbd-sources/include/ftl/rgbd/frameset.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/frameset.hpp
@@ -2,6 +2,7 @@
 #define _FTL_RGBD_FRAMESET_HPP_
 
 #include <ftl/threads.hpp>
+#include <ftl/timer.hpp>
 #include <ftl/rgbd/frame.hpp>
 
 #include <opencv2/opencv.hpp>
@@ -10,6 +11,10 @@
 namespace ftl {
 namespace rgbd {
 
+// Allows a latency of 20 frames maximum
+static const size_t kMaxFramesets = 15;
+static const size_t kMaxFramesInSet = 32;
+
 class Source;
 
 /**
@@ -18,21 +23,132 @@ class Source;
  * timestamp.
  */
 struct FrameSet {
+	int id=0;
 	int64_t timestamp;				// Millisecond timestamp of all frames
-	std::vector<Source*> sources;	// All source objects involved.
 	std::vector<ftl::rgbd::Frame> frames;
 	std::atomic<int> count;				// Number of valid frames
 	std::atomic<unsigned int> mask;		// Mask of all sources that contributed
 	bool stale;						// True if buffers have been invalidated
 	SHARED_MUTEX mtx;
 
-	void upload(ftl::codecs::Channels, cudaStream_t stream=0);
-	void download(ftl::codecs::Channels, cudaStream_t stream=0);
+	/**
+	 * Upload all specified host memory channels to GPU memory.
+	 */
+	void upload(ftl::codecs::Channels<0>, cudaStream_t stream=0);
+
+	/**
+	 * Download all specified GPU memory channels to host memory.
+	 */
+	void download(ftl::codecs::Channels<0>, cudaStream_t stream=0);
+
+	/**
+	 * Move the entire frameset to another frameset object. This will
+	 * invalidate the current frameset object as all memory buffers will be
+	 * moved.
+	 */
 	void swapTo(ftl::rgbd::FrameSet &);
 
+	/**
+	 * Clear all channels and all memory allocations within those channels.
+	 * This will perform a resetFull on all frames in the frameset.
+	 */
 	void resetFull();
 };
 
+/**
+ * Callback type for receiving video frames.
+ */
+typedef std::function<bool(ftl::rgbd::FrameSet &)> VideoCallback;
+
+/**
+ * Abstract class for any generator of FrameSet structures. A generator
+ * produces (decoded) frame sets at regular frame intervals depending on the
+ * global timer settings. The `onFrameSet` callback may be triggered from any
+ * thread and also may drop frames and not be called for a given timestamp.
+ */
+class Generator {
+	public:
+	Generator() {}
+	virtual ~Generator() {}
+
+	/** Number of frames in last frameset. This can change over time. */
+	virtual size_t size()=0;
+
+	/**
+	 * Get the persistent state object for a frame. An exception is thrown
+	 * for a bad index.
+	 */
+	virtual ftl::rgbd::FrameState &state(int ix)=0;
+
+	inline ftl::rgbd::FrameState &operator[](int ix) { return state(ix); }
+
+	/** Register a callback to receive new frame sets. */
+	virtual void onFrameSet(const ftl::rgbd::VideoCallback &)=0;
+};
+
+/**
+ * Accept frames and generate framesets as they become completed. This can
+ * directly act as a generator of framesets, each frameset being generated
+ * by the global timer. Once the expected number of frames have been received,
+ * a frameset is marked as complete and can then be passed to the callback at
+ * the appropriate time. If frames are generated faster than consumed then they
+ * are buffered and merged into a single frameset. The buffer has a limited size
+ * so a longer delay in a callback will cause buffer failures. If frames are
+ * generated below framerate then the on frameset callback is just not called.
+ */
+class Builder : public Generator {
+	public:
+	Builder();
+	~Builder();
+
+	size_t size() override;
+
+	ftl::rgbd::FrameState &state(int ix) override;
+
+	void onFrameSet(const ftl::rgbd::VideoCallback &) override;
+
+	/**
+	 * Add a new frame at a given timestamp.
+	 */
+	void push(int64_t timestamp, int ix, ftl::rgbd::Frame &f);
+
+	void setName(const std::string &name);
+
+	private:
+	std::list<FrameSet*> framesets_;  // Active framesets
+	std::list<FrameSet*> allocated_;  // Keep memory allocations
+
+	size_t head_;
+	ftl::rgbd::VideoCallback callback_;
+	MUTEX mutex_;
+	int mspf_;
+	float latency_;
+	float fps_;
+	int stats_count_;
+	int64_t last_ts_;
+	std::atomic<int> jobs_;
+	volatile bool skip_;
+	ftl::timer::TimerHandle main_id_;
+	size_t size_;
+	std::vector<ftl::rgbd::FrameState*> states_;
+
+	std::string name_;
+
+	/* Insert a new frameset into the buffer, along with all intermediate
+	 * framesets between the last in buffer and the new one.
+	 */
+	ftl::rgbd::FrameSet *_addFrameset(int64_t timestamp);
+
+	/* Find a frameset with given latency in frames. */
+	ftl::rgbd::FrameSet *_getFrameset();
+
+	/* Search for a matching frameset. */
+	ftl::rgbd::FrameSet *_findFrameset(int64_t ts);
+	void _freeFrameset(ftl::rgbd::FrameSet *);
+
+	void _recordStats(float fps, float latency);
+};
+
 }
 }
 
diff --git a/components/rgbd-sources/include/ftl/rgbd/group.hpp b/components/rgbd-sources/include/ftl/rgbd/group.hpp
index 8df9ab1f328368c929c5ce850cff11259bbd9d86..a4955d1d83e8eff268bca6c0ea99baa4e9907f59 100644
--- a/components/rgbd-sources/include/ftl/rgbd/group.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/group.hpp
@@ -20,9 +20,6 @@ namespace rgbd {
 
 class Source;
 
-// Allows a latency of 20 frames maximum
-static const size_t kMaxFramesets = 15;
-
 /**
  * Manage a group of RGB-D sources to obtain synchronised sets of frames from
  * those sources. The Group class provides a synchronised callback mechanism
@@ -33,7 +30,7 @@ static const size_t kMaxFramesets = 15;
  * then it will be used. This can be disabled. It is also possible to allow
  * incomplete frames to be used, but this is disabled by default.
  */
-class Group {
+class Group : public ftl::rgbd::Generator {
 	public:
 	Group();
 	~Group();
@@ -74,7 +71,7 @@ class Group {
 	 * the frameset are swapped during the function call, meaning that the
 	 * frameset data is no longer valid upon returning.
 	 */
-	void sync(std::function<bool(FrameSet &)>);
+	void onFrameSet(const VideoCallback &cb) override;
 
 	/**
 	 * Whenever any source within the group receives raw data, this callback
@@ -88,66 +85,33 @@ class Group {
 	/**
 	 * Removes a raw data callback from all sources in the group.
 	 */
-	void removeRawCallback(const std::function<void(ftl::rgbd::Source*, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt)> &);
+	//void removeRawCallback(const std::function<void(ftl::rgbd::Source*, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt)> &);
 
 	inline std::vector<Source*> sources() const { return sources_; }
 
-	/** @deprecated */
-	//bool getFrames(FrameSet &, bool complete=false);
+	size_t size() override { return builder_.size(); }
 
-	/** To be deprecated in favour of ftl::timer::setInterval.
-	 */
-	//void setFPS(int fps);
-
-	/**
-	 * Set the minimum number of frames latency. The latency is from the most
-	 * recent frame obtained, meaning that the timestamp of the input frames is
-	 * the reference point, this may already be several frames old. Latency
-	 * does not correspond to actual current time.
-	 */
-	void setLatency(int frames) { }
+	ftl::rgbd::FrameState &state(int ix) override { return builder_.state(ix); }
 
 	void stop() {}
 
 	int streamID(const ftl::rgbd::Source *s) const;
 
 	private:
-	std::list<FrameSet*> framesets_;  // Active framesets
-	std::list<FrameSet*> allocated_;  // Keep memory allocations
-
+	ftl::rgbd::Builder builder_;
 	std::vector<Source*> sources_;
 	ftl::operators::Graph *pipeline_;
-	size_t head_;
-	std::function<bool(FrameSet &)> callback_;
-	MUTEX mutex_;
-	int mspf_;
-	float latency_;
-	float fps_;
-	int stats_count_;
-	int64_t last_ts_;
+	
 	std::atomic<int> jobs_;
 	volatile bool skip_;
 	ftl::timer::TimerHandle cap_id_;
 	ftl::timer::TimerHandle swap_id_;
 	ftl::timer::TimerHandle main_id_;
 	std::string name_;
-
-	/* Insert a new frameset into the buffer, along with all intermediate
-	 * framesets between the last in buffer and the new one.
-	 */
-	ftl::rgbd::FrameSet *_addFrameset(int64_t timestamp);
+	MUTEX mutex_;
 
 	void _retrieveJob(ftl::rgbd::Source *);
 	void _computeJob(ftl::rgbd::Source *);
-
-	/* Find a frameset with given latency in frames. */
-	ftl::rgbd::FrameSet *_getFrameset();
-
-	/* Search for a matching frameset. */
-	ftl::rgbd::FrameSet *_findFrameset(int64_t ts);
-	void _freeFrameset(ftl::rgbd::FrameSet *);
-
-	void _recordStats(float fps, float latency);
 };
 
 }
diff --git a/components/rgbd-sources/include/ftl/rgbd/source.hpp b/components/rgbd-sources/include/ftl/rgbd/source.hpp
index dc41f10a125584f9a070a2025d78c9f22d07b0d0..23fff94401e9f2bb27420cd3373c96781b68592b 100644
--- a/components/rgbd-sources/include/ftl/rgbd/source.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/source.hpp
@@ -155,7 +155,7 @@ class Source : public ftl::Configurable {
 	/**
 	 * Get the camera position as a pose matrix.
 	 */
-	const Eigen::Matrix4d &getPose() const;
+	[[deprecated]] const Eigen::Matrix4d &getPose() const;
 
 	/**
 	 * Check what features this source has available.
@@ -173,6 +173,8 @@ class Source : public ftl::Configurable {
 
 	std::string getURI() { return value("uri", std::string("")); }
 
+	ftl::rgbd::FrameState &state() { return impl_->state_; }
+
 	//void customImplementation(detail::Source *);
 
 	SHARED_MUTEX &mutex() { return mutex_; }
@@ -267,11 +269,11 @@ void ftl::rgbd::Source::inject(ftl::codecs::Channel c, ARGS... args) {
 
 	spkt.timestamp = impl_->timestamp_;
 	spkt.channel = c;
-	spkt.channel_count = 0;
+	spkt.frame_number = 0;
 	spkt.streamID = 0;
 	pkt.codec = ftl::codecs::codec_t::MSGPACK;
-	pkt.block_number = 0;
-	pkt.block_total = 1;
+	pkt.bitrate = 0;
+	pkt.frame_count = 1;
 	pkt.definition = ftl::codecs::definition_t::Any;
 	pkt.flags = 0;
 
diff --git a/components/rgbd-sources/include/ftl/rgbd/streamer.hpp b/components/rgbd-sources/include/ftl/rgbd/streamer.hpp
index a33a6d174f2fec98f660e9fcd7996ae66162d163..f6fc1a044a98cd0faa5235772617741920d7ca21 100644
--- a/components/rgbd-sources/include/ftl/rgbd/streamer.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/streamer.hpp
@@ -94,7 +94,7 @@ enum encoder_t {
  */
 class Streamer : public ftl::Configurable {
 	public:
-	Streamer(nlohmann::json &config, ftl::net::Universe *net);
+	[[deprecated]] Streamer(nlohmann::json &config, ftl::net::Universe *net);
 	~Streamer();
 
 	/**
@@ -125,8 +125,6 @@ class Streamer : public ftl::Configurable {
 
 	void wait();
 
-	void setLatency(int l) { group_.setLatency(l); }
-
 	/**
 	 * Alternative to calling run(), it will operate a single frame capture,
 	 * compress and stream cycle.
@@ -138,6 +136,7 @@ class Streamer : public ftl::Configurable {
 	private:
 	ftl::rgbd::Group group_;
 	std::map<std::string, detail::StreamSource*> sources_;
+	std::vector<detail::StreamSource*> sourcesByNum_;
 	std::list<ftl::rgbd::Group*> proxy_grps_;
 	//ctpl::thread_pool pool_;
 	SHARED_MUTEX mutex_;
@@ -151,7 +150,7 @@ class Streamer : public ftl::Configurable {
 	int64_t frame_no_;
 	bool insert_iframes_;
 
-	encoder_t encode_mode_;
+	ftl::codecs::Channel second_channel_;
 
 	int64_t mspf_;
 	float actual_fps_;
diff --git a/components/rgbd-sources/src/frame.cpp b/components/rgbd-sources/src/frame.cpp
index c1b76e117852e30d61f1e79e2e88882ee6855981..a0b9be98b1c00ed45ae5f65953d41fbd8eabec12 100644
--- a/components/rgbd-sources/src/frame.cpp
+++ b/components/rgbd-sources/src/frame.cpp
@@ -2,24 +2,90 @@
 #include <ftl/rgbd/frame.hpp>
 
 using ftl::rgbd::Frame;
+using ftl::rgbd::FrameState;
 using ftl::codecs::Channels;
 using ftl::codecs::Channel;
 
 static cv::Mat none;
 static cv::cuda::GpuMat noneGPU;
 
+FrameState::FrameState() : camera_left_({0}), camera_right_({0}), config_(nlohmann::json::value_t::object) {
+	pose_ = Eigen::Matrix4d::Identity();
+}
+
+FrameState::FrameState(FrameState &f) {
+	pose_ = f.pose_;
+	camera_left_ = f.camera_left_;
+	camera_right_ = f.camera_right_;
+	changed_ = f.changed_;
+	config_ = f.config_;
+	// TODO: Add mutex lock
+	f.changed_.clear();
+}
+
+FrameState::FrameState(FrameState &&f) {
+	pose_ = f.pose_;
+	camera_left_ = f.camera_left_;
+	camera_right_ = f.camera_right_;
+	changed_ = f.changed_;
+	config_ = std::move(f.config_);
+	// TODO: Add mutex lock
+	f.changed_.clear();
+}
+
+FrameState &FrameState::operator=(FrameState &f) {
+	pose_ = f.pose_;
+	camera_left_ = f.camera_left_;
+	camera_right_ = f.camera_right_;
+	changed_ = f.changed_;
+	config_ = f.config_;
+	// TODO: Add mutex lock
+	f.changed_.clear();
+	return *this;
+}
+
+FrameState &FrameState::operator=(FrameState &&f) {
+	pose_ = f.pose_;
+	camera_left_ = f.camera_left_;
+	camera_right_ = f.camera_right_;
+	changed_ = f.changed_;
+	config_ = std::move(f.config_);
+	// TODO: Add mutex lock
+	f.changed_.clear();
+	return *this;
+}
+
+void FrameState::setPose(const Eigen::Matrix4d &pose) {
+	pose_ = pose;
+	changed_ += Channel::Pose;
+}
+
+void FrameState::setLeft(const ftl::rgbd::Camera &p) {
+	camera_left_ = p;
+	changed_ += Channel::Calibration;
+}
+
+void FrameState::setRight(const ftl::rgbd::Camera &p) {
+	camera_right_ = p;
+	changed_ += Channel::Calibration2;
+}
+
+// =============================================================================
+
 void Frame::reset() {
+	origin_ = nullptr;
 	channels_.clear();
 	gpu_.clear();
-	for (size_t i=0u; i<Channels::kMax; ++i) {
+	for (size_t i=0u; i<Channels<0>::kMax; ++i) {
 		data_[i].encoded.clear();
 	}
 }
 
 void Frame::resetFull() {
+	origin_ = nullptr;
 	channels_.clear();
 	gpu_.clear();
-	for (size_t i=0u; i<Channels::kMax; ++i) {
+	for (size_t i=0u; i<Channels<0>::kMax; ++i) {
 		data_[i].gpu = cv::cuda::GpuMat();
 		data_[i].host = cv::Mat();
 		data_[i].encoded.clear();
@@ -34,8 +100,8 @@ void Frame::upload(Channel c, cv::cuda::Stream stream) {
 	upload(Channels(c), stream);
 }
 
-void Frame::download(Channels c, cv::cuda::Stream stream) {
-	for (size_t i=0u; i<Channels::kMax; ++i) {
+void Frame::download(Channels<0> c, cv::cuda::Stream stream) {
+	for (size_t i=0u; i<Channels<0>::kMax; ++i) {
 		if (c.has(i) && channels_.has(i) && gpu_.has(i)) {
 			data_[i].gpu.download(data_[i].host, stream);
 			gpu_ -= i;
@@ -43,8 +109,8 @@ void Frame::download(Channels c, cv::cuda::Stream stream) {
 	}
 }
 
-void Frame::upload(Channels c, cv::cuda::Stream stream) {
-	for (size_t i=0u; i<Channels::kMax; ++i) {
+void Frame::upload(Channels<0> c, cv::cuda::Stream stream) {
+	for (size_t i=0u; i<Channels<0>::kMax; ++i) {
 		if (c.has(i) && channels_.has(i) && !gpu_.has(i)) {
 			data_[i].gpu.upload(data_[i].host, stream);
 			gpu_ += i;
@@ -83,15 +149,17 @@ void Frame::mergeEncoding(ftl::rgbd::Frame &f) {
 	}
 }
 
-bool Frame::empty(ftl::codecs::Channels channels) {
+bool Frame::empty(ftl::codecs::Channels<0> channels) {
 	for (auto c : channels) {
 		if (empty(c)) return true;
 	}
 	return false;
 }
 
-void Frame::swapTo(ftl::codecs::Channels channels, Frame &f) {
+void Frame::swapTo(ftl::codecs::Channels<0> channels, Frame &f) {
 	f.reset();
+	f.origin_ = origin_;
+	f.state_ = state_;
 
 	// For all channels in this frame object
 	for (auto c : channels_) {
@@ -141,8 +209,10 @@ void Frame::swapChannels(ftl::codecs::Channel a, ftl::codecs::Channel b) {
 	}
 }
 
-void Frame::copyTo(ftl::codecs::Channels channels, Frame &f) {
+void Frame::copyTo(ftl::codecs::Channels<0> channels, Frame &f) {
 	f.reset();
+	f.origin_ = origin_;
+	f.state_ = state_;
 
 	// For all channels in this frame object
 	for (auto c : channels_) {
@@ -228,6 +298,32 @@ template<> const cv::cuda::GpuMat& Frame::get(ftl::codecs::Channel channel) cons
 	return _get(channel).gpu;
 }
 
+template<> const Eigen::Matrix4d& Frame::get(ftl::codecs::Channel channel) const {
+	if (channel == Channel::Pose) {
+		return state_.getPose();
+	}
+
+	throw ftl::exception(ftl::Formatter() << "Invalid pose channel: " << (int)channel);
+}
+
+template<> const ftl::rgbd::Camera& Frame::get(ftl::codecs::Channel channel) const {
+	if (channel == Channel::Calibration) {
+		return state_.getLeft();
+	} else if (channel == Channel::Calibration2) {
+		return state_.getRight();
+	}
+
+	throw ftl::exception(ftl::Formatter() << "Invalid calibration channel: " << (int)channel);
+}
+
+template<> const nlohmann::json& Frame::get(ftl::codecs::Channel channel) const {
+	if (channel == Channel::Configuration) {
+		return state_.getConfig();
+	}
+
+	throw ftl::exception(ftl::Formatter() << "Invalid configuration channel: " << (int)channel);
+}
+
 template <> cv::Mat &Frame::create(ftl::codecs::Channel c, const ftl::rgbd::FormatBase &f) {
 	if (c == Channel::None) {
 		throw ftl::exception("Cannot create a None channel");
@@ -264,6 +360,11 @@ template <> cv::cuda::GpuMat &Frame::create(ftl::codecs::Channel c, const ftl::r
 	return m.gpu;
 }
 
+void Frame::clearPackets(ftl::codecs::Channel c) {
+	auto &m = _get(c);
+	m.encoded.clear();
+}
+
 template <> cv::Mat &Frame::create(ftl::codecs::Channel c) {
 	if (c == Channel::None) {
 		throw ftl::exception("Cannot create a None channel");
@@ -297,3 +398,40 @@ void Frame::resetTexture(ftl::codecs::Channel c) {
 	m.tex.free();
 }
 
+void Frame::setOrigin(ftl::rgbd::FrameState *state) {
+	if (origin_ != nullptr) {
+		throw ftl::exception("Can only set origin once after reset");
+	}
+
+	origin_ = state;
+	state_ = *state;
+}
+
+const Eigen::Matrix4d &Frame::getPose() const {
+	return get<Eigen::Matrix4d>(ftl::codecs::Channel::Pose);
+}
+
+const ftl::rgbd::Camera &Frame::getLeftCamera() const {
+	return get<ftl::rgbd::Camera>(ftl::codecs::Channel::Calibration);
+}
+
+const ftl::rgbd::Camera &Frame::getRightCamera() const {
+	return get<ftl::rgbd::Camera>(ftl::codecs::Channel::Calibration2);
+}
+
+void ftl::rgbd::Frame::setPose(const Eigen::Matrix4d &pose) {
+	if (origin_) origin_->setPose(pose);
+}
+
+void ftl::rgbd::Frame::setLeftCamera(const ftl::rgbd::Camera &c) {
+	if (origin_) origin_->setLeft(c);
+}
+
+void ftl::rgbd::Frame::setRightCamera(const ftl::rgbd::Camera &c) {
+	if (origin_) origin_->setRight(c);
+}
+
+std::string ftl::rgbd::Frame::getConfigString() const {
+	return get<nlohmann::json>(ftl::codecs::Channel::Configuration).dump();
+}
+
diff --git a/components/rgbd-sources/src/frameset.cpp b/components/rgbd-sources/src/frameset.cpp
index 44be010a344210ca2fe3cbc206a06155ee57c0ff..9cda59838d92f41d0d10e699b3bd7cb530947345 100644
--- a/components/rgbd-sources/src/frameset.cpp
+++ b/components/rgbd-sources/src/frameset.cpp
@@ -1,16 +1,25 @@
 #include <ftl/rgbd/frameset.hpp>
+#include <ftl/timer.hpp>
 
+#include <chrono>
+
+using ftl::rgbd::Builder;
+using ftl::rgbd::kMaxFramesets;
+using ftl::rgbd::kMaxFramesInSet;
+using std::vector;
+using std::chrono::milliseconds;
+using std::this_thread::sleep_for;
+using ftl::codecs::Channel;
 using ftl::rgbd::FrameSet;
 using ftl::codecs::Channels;
-using ftl::codecs::Channel;
 
-void FrameSet::upload(ftl::codecs::Channels c, cudaStream_t stream) {
+void FrameSet::upload(ftl::codecs::Channels<0> c, cudaStream_t stream) {
 	for (auto &f : frames) {
 		f.upload(c, stream);
 	}
 }
 
-void FrameSet::download(ftl::codecs::Channels c, cudaStream_t stream) {
+void FrameSet::download(ftl::codecs::Channels<0> c, cudaStream_t stream) {
 	for (auto &f : frames) {
 		f.download(c, stream);
 	}
@@ -21,8 +30,6 @@ void FrameSet::swapTo(ftl::rgbd::FrameSet &fs) {
 
 	//if (fs.frames.size() != frames.size()) {
 		// Assume "this" is correct and "fs" is not.
-		fs.sources.clear();
-		for (auto s : sources) fs.sources.push_back(s);
 		fs.frames.resize(frames.size());
 	//}
 
@@ -32,7 +39,7 @@ void FrameSet::swapTo(ftl::rgbd::FrameSet &fs) {
 	fs.mask = static_cast<unsigned int>(mask);
 
 	for (size_t i=0; i<frames.size(); ++i) {
-		frames[i].swapTo(Channels::All(), fs.frames[i]);
+		frames[i].swapTo(Channels<0>::All(), fs.frames[i]);
 	}
 
 	stale = true;
@@ -45,3 +52,248 @@ void FrameSet::resetFull() {
 		f.resetFull();
 	}
 }
+
+// =============================================================================
+
+Builder::Builder() : head_(0) {
+	jobs_ = 0;
+	skip_ = false;
+	//setFPS(20);
+	size_ = 0;
+
+	mspf_ = ftl::timer::getInterval();
+	name_ = "NoName";
+
+	latency_ = 0.0f;;
+	stats_count_ = 0;
+	fps_ = 0.0f;
+}
+
+Builder::~Builder() {
+	main_id_.cancel();
+
+	UNIQUE_LOCK(mutex_, lk);
+	// Make sure all jobs have finished
+	while (jobs_ > 0) {
+		sleep_for(milliseconds(10));
+	}
+}
+
+
+void Builder::push(int64_t timestamp, int ix, ftl::rgbd::Frame &frame) {
+	if (timestamp <= 0 || ix < 0 || ix >= kMaxFramesInSet) return;
+
+	UNIQUE_LOCK(mutex_, lk);
+
+	// Size is determined by largest frame index received... note that size
+	// cannot therefore reduce.
+	if (ix >= size_) {
+		size_ = ix+1;
+		states_.resize(size_);
+	}
+	states_[ix] = frame.origin();
+
+	auto *fs = _findFrameset(timestamp);
+
+	if (!fs) {
+		// Add new frameset
+		fs = _addFrameset(timestamp);
+		if (!fs) return;
+	}
+
+	if (fs->frames.size() < size_) fs->frames.resize(size_);
+
+	lk.unlock();
+	SHARED_LOCK(fs->mtx, lk2);
+
+	frame.swapTo(ftl::codecs::kAllChannels, fs->frames[ix]);
+
+	++fs->count;
+
+	if (fs->mask & (1 << ix)) {
+		LOG(ERROR) << "Too many frames received for given timestamp: " << timestamp << " (source " << ix << ")";
+	}
+	fs->mask |= (1 << ix);
+}
+
+size_t Builder::size() {
+	return size_;
+}
+
+void Builder::onFrameSet(const std::function<bool(ftl::rgbd::FrameSet &)> &cb) {
+	// 3. Issue IO retrieve ad compute jobs before finding a valid
+	// frame at required latency to pass to callback.
+	main_id_ = ftl::timer::add(ftl::timer::kTimerMain, [this,cb](int64_t ts) {
+		//if (jobs_ > 0) LOG(ERROR) << "SKIPPING TIMER JOB " << ts;
+		if (jobs_ > 0) return true;
+		jobs_++;
+
+		// Find a previous frameset and specified latency and do the sync
+		// callback with that frameset.
+		//if (latency_ > 0) {
+			ftl::rgbd::FrameSet *fs = nullptr;
+	
+			UNIQUE_LOCK(mutex_, lk);
+			fs = _getFrameset();
+
+			//LOG(INFO) << "Latency for " << name_ << " = " << (latency_*ftl::timer::getInterval()) << "ms";
+
+			if (fs) {
+				UNIQUE_LOCK(fs->mtx, lk2);
+				lk.unlock();
+				// The buffers are invalid after callback so mark stale
+				fs->stale = true;
+
+				//LOG(INFO) << "PROCESS FRAMESET";
+
+				//ftl::pool.push([this,fs,cb](int) {
+					try {
+						cb(*fs);
+						//LOG(INFO) << "Frameset processed (" << name_ << "): " << fs->timestamp;
+					} catch(std::exception &e) {
+						LOG(ERROR) << "Exception in group sync callback: " << e.what();
+					}
+
+					//fs->resetFull();
+
+					lk.lock();
+					_freeFrameset(fs);
+
+					jobs_--;
+				//});
+			} else {
+				//LOG(INFO) << "NO FRAME FOUND: " << name_ << " " << size_;
+				//latency_++;
+				jobs_--;
+			}
+		//}
+
+		//if (jobs_ == 0) LOG(INFO) << "LAST JOB =  Main";
+		return true;
+	});
+}
+
+ftl::rgbd::FrameState &Builder::state(int ix) {
+	UNIQUE_LOCK(mutex_, lk);
+	if (ix < 0 || ix >= states_.size()) {
+		throw ftl::exception("Frame state out-of-bounds");
+	}
+	if (!states_[ix]) throw ftl::exception("Missing framestate");
+	return *states_[ix];
+}
+
+static void mergeFrameset(ftl::rgbd::FrameSet &f1, ftl::rgbd::FrameSet &f2) {
+	// Prepend all frame encodings in f2 into corresponding frame in f1.
+	for (int i=0; i<f1.frames.size(); ++i) {
+		f1.frames[i].mergeEncoding(f2.frames[i]);
+	}
+}
+
+void Builder::_recordStats(float fps, float latency) {
+	latency_ += latency;
+	fps_ += fps;
+	++stats_count_;
+
+	if (fps_/float(stats_count_) <= float(stats_count_)) {
+		fps_ /= float(stats_count_);
+		latency_ /= float(stats_count_);
+		LOG(INFO) << name_ << ": fps = " << fps_ << ", latency = " << latency_;
+		fps_ = 0.0f;
+		latency_ = 0.0f;
+		stats_count_ = 0;
+	}
+}
+
+ftl::rgbd::FrameSet *Builder::_findFrameset(int64_t ts) {
+	// Search backwards to find match
+	for (auto f : framesets_) {
+		if (f->timestamp == ts) {
+			return f;
+		} else if (f->timestamp < ts) {
+			return nullptr;
+		}
+	}
+
+	return nullptr;
+}
+
+/*
+ * Get the most recent completed frameset that isn't stale.
+ * Note: Must occur inside a mutex lock.
+ */
+ftl::rgbd::FrameSet *Builder::_getFrameset() {
+	for (auto i=framesets_.begin(); i!=framesets_.end(); i++) {
+		auto *f = *i;
+		if (!f->stale && f->count >= size_) {
+			//LOG(INFO) << "GET FRAMESET and remove: " << f->timestamp;
+			auto j = framesets_.erase(i);
+			
+			int count = 0;
+			// Merge all previous frames
+			for (; j!=framesets_.end(); j++) {
+				++count;
+
+				auto *f2 = *j;
+				j = framesets_.erase(j);
+				mergeFrameset(*f,*f2);
+				_freeFrameset(f2);
+			}
+
+			//if (count > 0) LOG(INFO) << "COUNT = " << count;
+
+			int64_t now = ftl::timer::get_time();
+			float framerate = 1000.0f / float(now - last_ts_);
+			_recordStats(framerate, now - f->timestamp);
+			last_ts_ = now;
+			return f;
+		}
+	}
+
+	return nullptr;
+}
+
+void Builder::_freeFrameset(ftl::rgbd::FrameSet *fs) {
+	allocated_.push_back(fs);
+}
+
+ftl::rgbd::FrameSet *Builder::_addFrameset(int64_t timestamp) {
+	if (allocated_.size() == 0) {
+		if (framesets_.size() < kMaxFramesets) {
+			allocated_.push_back(new ftl::rgbd::FrameSet);
+		} else {
+			LOG(ERROR) << "Could not allocate framesetL: " << timestamp;
+			return nullptr;
+		}
+	}
+	FrameSet *newf = allocated_.front();
+	allocated_.pop_front();
+
+	newf->timestamp = timestamp;
+	newf->id = 0;
+	newf->count = 0;
+	newf->mask = 0;
+	newf->stale = false;
+	newf->frames.resize(size_);
+
+	for (auto &f : newf->frames) f.reset();
+
+	// Insertion sort by timestamp
+	for (auto i=framesets_.begin(); i!=framesets_.end(); i++) {
+		auto *f = *i;
+
+		if (timestamp > f->timestamp) {
+			framesets_.insert(i, newf);
+			return newf;
+		}
+	}
+
+	framesets_.push_back(newf);
+	return newf;
+}
+
+void Builder::setName(const std::string &name) {
+	name_ = name;
+}
+
+
+
diff --git a/components/rgbd-sources/src/group.cpp b/components/rgbd-sources/src/group.cpp
index ca64d4764def2c4a2c5c79b96798d916eac6e6a9..409559d5881b55ff1826c12e5b5ea29852743a1a 100644
--- a/components/rgbd-sources/src/group.cpp
+++ b/components/rgbd-sources/src/group.cpp
@@ -13,29 +13,10 @@ using std::chrono::milliseconds;
 using std::this_thread::sleep_for;
 using ftl::codecs::Channel;
 
-Group::Group() : pipeline_(nullptr), head_(0) {
-	//framesets_[0].timestamp = -1;
-	//framesets_[0].stale = true;
-
-	/*for (auto &i : framesets_) {
-		i.stale = true;
-	}*/
-
-	// Allocate some initial framesets
-	//for (int i=0; i<10; ++i) {
-	//	allocated_.push_back(new ftl::rgbd::FrameSet);
-	//}
-
+Group::Group() : pipeline_(nullptr) {
 	jobs_ = 0;
 	skip_ = false;
-	//setFPS(20);
-
-	mspf_ = ftl::timer::getInterval();
 	name_ = "NoName";
-
-	latency_ = 0.0f;;
-	stats_count_ = 0;
-	fps_ = 0.0f;
 }
 
 Group::~Group() {
@@ -54,80 +35,17 @@ Group::~Group() {
 	}
 }
 
-//void Group::setFPS(int fps) {
-//	mspf_ = 1000 / fps;
-//	ftl::timer::setInterval(mspf_);
-//}
-
 void Group::addSource(ftl::rgbd::Source *src) {
 	UNIQUE_LOCK(mutex_, lk);
 	size_t ix = sources_.size();
 	sources_.push_back(src);
 
 	src->setCallback([this,ix,src](int64_t timestamp, ftl::rgbd::Frame &frame) {
-		if (timestamp == 0) return;
-
-		auto chan = src->getChannel();
-
-		//LOG(INFO) << "SRC CB (" << name_ << "): " << timestamp << " (" << ")";
-
-		UNIQUE_LOCK(mutex_, lk);
-		auto *fs = _findFrameset(timestamp);
-
-		if (!fs) {
-			// Add new frameset
-			fs = _addFrameset(timestamp);
-
-			if (!fs) return;
-		} /*else if (framesets_[(head_+1)%kFrameBufferSize].timestamp > timestamp) {
-			// Too old, just ditch it
-			LOG(WARNING) << "Received frame too old for buffer";
-			return;
-		}*/
-
-		// Search backwards to find match
-		//for (size_t i=0; i<kFrameBufferSize; ++i) {
-		//	FrameSet &fs = framesets_[(head_+kFrameBufferSize-i) % kFrameBufferSize];
-			//if (fs.timestamp == timestamp) {
-				lk.unlock();
-				SHARED_LOCK(fs->mtx, lk2);
-
-				frame.swapTo(ftl::codecs::kAllChannels, fs->frames[ix]);
-
-				if (fs->count+1 == sources_.size()) {
-					if (pipeline_) {
-						pipeline_->apply(*fs, *fs, 0);
-					}
-				}
-
-				++fs->count;
-				fs->mask |= (1 << ix);
-
-				if (fs->count == sources_.size()) {
-					//LOG(INFO) << "COMPLETE SET (" << name_ << "): " << fs->timestamp;
-				} else if (fs->count > sources_.size()) {
-					LOG(ERROR) << "Too many frames for frame set: " << fs->timestamp << " sources=" << sources_.size();
-				} else {
-					//LOG(INFO) << "INCOMPLETE SET ("  << ix << "): " << fs.timestamp;
-				}
-
-				/*if (callback_ && fs->count == sources_.size()) {
-					try {
-						if (callback_(fs)) {
-							// TODO: Remove callback if returns false?
-						}
-					} catch (...) {
-						LOG(ERROR) << "Exception in group callback";
-					}
-
-					// Reset count to prevent multiple reads of these frames
-					//fs.count = 0;
-				}*/
-
-				return;
-			//}
-		//}
-		//LOG(WARNING) << "Frame timestamp not found in buffer";
+		// FIXME: Not safe for multiple sources
+		if (pipeline_) {
+			pipeline_->apply(frame, frame, 0);
+		}
+		builder_.push(timestamp, ix, frame);
 	});
 }
 
@@ -166,7 +84,7 @@ int Group::streamID(const ftl::rgbd::Source *s) const {
 	return -1;
 }
 
-void Group::sync(std::function<bool(ftl::rgbd::FrameSet &)> cb) {
+void Group::onFrameSet(const ftl::rgbd::VideoCallback &cb) {
 	//if (latency_ == 0) {
 	//	callback_ = cb;
 	//}
@@ -198,7 +116,7 @@ void Group::sync(std::function<bool(ftl::rgbd::FrameSet &)> cb) {
 	main_id_ = ftl::timer::add(ftl::timer::kTimerMain, [this,cb](int64_t ts) {
 		//if (skip_) LOG(ERROR) << "SKIPPING TIMER JOB " << ts;
 		if (skip_) return true;
-		jobs_++;
+		//jobs_++;
 
 		for (auto s : sources_) {
 			jobs_ += 2;
@@ -214,46 +132,11 @@ void Group::sync(std::function<bool(ftl::rgbd::FrameSet &)> cb) {
 				--jobs_;
 			});
 		}
+		return true;
+	});
 
-		// Find a previous frameset and specified latency and do the sync
-		// callback with that frameset.
-		//if (latency_ > 0) {
-			ftl::rgbd::FrameSet *fs = nullptr;
-	
-			UNIQUE_LOCK(mutex_, lk);
-			fs = _getFrameset();
-
-			//LOG(INFO) << "Latency for " << name_ << " = " << (latency_*ftl::timer::getInterval()) << "ms";
-
-			if (fs) {
-				UNIQUE_LOCK(fs->mtx, lk2);
-				lk.unlock();
-				// The buffers are invalid after callback so mark stale
-				fs->stale = true;
-
-				//ftl::pool.push([this,fs,cb](int) {
-					try {
-						cb(*fs);
-						//LOG(INFO) << "Frameset processed (" << name_ << "): " << fs->timestamp;
-					} catch(std::exception &e) {
-						LOG(ERROR) << "Exception in group sync callback: " << e.what();
-					}
-
-					//fs->resetFull();
-
-					lk.lock();
-					_freeFrameset(fs);
-
-					jobs_--;
-				//});
-			} else {
-				//LOG(INFO) << "NO FRAME FOUND: " << last_ts_ - latency_*mspf_;
-				//latency_++;
-				jobs_--;
-			}
-		//}
-
-		//if (jobs_ == 0) LOG(INFO) << "LAST JOB =  Main";
+	builder_.onFrameSet([this,cb](ftl::rgbd::FrameSet &fs) {
+		cb(fs);
 		return true;
 	});
 }
@@ -264,186 +147,15 @@ void Group::addRawCallback(const std::function<void(ftl::rgbd::Source*, const ft
 	}
 }
 
-void Group::removeRawCallback(const std::function<void(ftl::rgbd::Source*, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt)> &f) {
+/*void Group::removeRawCallback(const std::function<void(ftl::rgbd::Source*, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt)> &f) {
 	for (auto s : sources_) {
 		s->removeRawCallback(f);
 	}
-}
-
-static void mergeFrameset(ftl::rgbd::FrameSet &f1, ftl::rgbd::FrameSet &f2) {
-	// Prepend all frame encodings in f2 into corresponding frame in f1.
-	for (int i=0; i<f1.frames.size(); ++i) {
-		f1.frames[i].mergeEncoding(f2.frames[i]);
-	}
-}
-
-void Group::_recordStats(float fps, float latency) {
-	latency_ += latency;
-	fps_ += fps;
-	++stats_count_;
-
-	if (fps_/float(stats_count_) <= float(stats_count_)) {
-		fps_ /= float(stats_count_);
-		latency_ /= float(stats_count_);
-		LOG(INFO) << name_ << ": fps = " << fps_ << ", latency = " << latency_;
-		fps_ = 0.0f;
-		latency_ = 0.0f;
-		stats_count_ = 0;
-	}
-}
-
-ftl::rgbd::FrameSet *Group::_findFrameset(int64_t ts) {
-	// Search backwards to find match
-	for (auto f : framesets_) {
-		if (f->timestamp == ts) {
-			return f;
-		} else if (f->timestamp < ts) {
-			return nullptr;
-		}
-	}
-
-	return nullptr;
-}
-
-/*
- * Get the most recent completed frameset that isn't stale.
- * Note: Must occur inside a mutex lock.
- */
-ftl::rgbd::FrameSet *Group::_getFrameset() {
-	for (auto i=framesets_.begin(); i!=framesets_.end(); i++) {
-		auto *f = *i;
-		if (!f->stale && f->count == sources_.size()) {
-			//LOG(INFO) << "GET FRAMESET and remove: " << f->timestamp;
-			auto j = framesets_.erase(i);
-			
-			int count = 0;
-			// Merge all previous frames
-			for (; j!=framesets_.end(); j++) {
-				++count;
-
-				auto *f2 = *j;
-				j = framesets_.erase(j);
-				mergeFrameset(*f,*f2);
-				_freeFrameset(f2);
-			}
-
-			//if (count > 0) LOG(INFO) << "COUNT = " << count;
-
-			int64_t now = ftl::timer::get_time();
-			float framerate = 1000.0f / float(now - last_ts_);
-			_recordStats(framerate, now - f->timestamp);
-			last_ts_ = now;
-			return f;
-		}
-	}
-
-	return nullptr;
-}
-
-void Group::_freeFrameset(ftl::rgbd::FrameSet *fs) {
-	allocated_.push_back(fs);
-}
-
-ftl::rgbd::FrameSet *Group::_addFrameset(int64_t timestamp) {
-	if (allocated_.size() == 0) {
-		if (framesets_.size() < kMaxFramesets) {
-			allocated_.push_back(new ftl::rgbd::FrameSet);
-		} else {
-			LOG(ERROR) << "Could not allocate frameset.";
-			return nullptr;
-		}
-	}
-	FrameSet *newf = allocated_.front();
-	allocated_.pop_front();
-
-	newf->timestamp = timestamp;
-	newf->count = 0;
-	newf->mask = 0;
-	newf->stale = false;
-	newf->frames.resize(sources_.size());
-
-	for (auto &f : newf->frames) f.reset();
-
-	if (newf->sources.size() != sources_.size()) {
-		newf->sources.clear();
-		for (auto s : sources_) newf->sources.push_back(s);
-	}
-
-	// Insertion sort by timestamp
-	for (auto i=framesets_.begin(); i!=framesets_.end(); i++) {
-		auto *f = *i;
-
-		if (timestamp > f->timestamp) {
-			framesets_.insert(i, newf);
-			return newf;
-		}
-	}
-
-	framesets_.push_back(newf);
-	return newf;
-
-
-	//int count = (framesets_[head_].timestamp == -1) ? 1 : (timestamp - framesets_[head_].timestamp) / mspf_;
-	//LOG(INFO) << "Massive timestamp difference: " << count;
-
-	// Allow for massive timestamp changes (Windows clock adjust)
-	// Only add a single frameset for large changes
-	//if (count < -int(kFrameBufferSize) || count >= kFrameBufferSize-1) {
-	/*if (framesets_[head_].timestamp < timestamp) {
-		head_ = (head_+1) % kFrameBufferSize;
-
-		//if (framesets_[head_].stale == false) LOG(FATAL) << "Buffer exceeded";
-
-		#ifdef DEBUG_MUTEX
-		std::unique_lock<std::shared_timed_mutex> lk(framesets_[head_].mtx, std::defer_lock);
-		#else
-		std::unique_lock<std::shared_mutex> lk(framesets_[head_].mtx, std::defer_lock);
-		#endif
-		if (!lk.try_lock()) {
-			LOG(ERROR) << "Frameset in use!!";
-			return;
-		}
-		framesets_[head_].timestamp = timestamp;
-		framesets_[head_].count = 0;
-		framesets_[head_].mask = 0;
-		framesets_[head_].stale = false;
-		framesets_[head_].frames.resize(sources_.size());
-
-		for (auto &f : framesets_[head_].frames) f.reset();
-
-		if (framesets_[head_].sources.size() != sources_.size()) {
-			framesets_[head_].sources.clear();
-			for (auto s : sources_) framesets_[head_].sources.push_back(s);
-		}
-
-		// Find number of valid frames that will be skipped
-		int count = 0;
-		//for (int j=1; j<kFrameBufferSize; ++j) {
-			int idx2 = (head_+kFrameBufferSize-1)%kFrameBufferSize;
-			//if (framesets_[idx2].stale || framesets_[idx2].timestamp <= 0) break;
-			++count;
-
-			// Make sure encoded packets are moved from skipped frames
-			//if (framesets_[idx2].count >= framesets_[idx2].sources.size()) {
-			if (framesets_[idx2].stale == false) {
-				mergeFrameset(framesets_[head_], framesets_[idx2]);
-				framesets_[idx2].stale = true;
-				framesets_[idx2].timestamp = -1;
-			}
-		//}
-
-		float framerate = 1000.0f / float((count+1)*ftl::timer::getInterval());
-		_recordStats(framerate, ftl::timer::get_time() - timestamp);
-
-		return;
-	} else {
-		LOG(ERROR) << "Old frame received: " << (framesets_[head_].timestamp - timestamp);
-		return;
-	}*/
-}
+}*/
 
 void Group::setName(const std::string &name) {
 	name_ = name;
+	builder_.setName(name);
 }
 
 
diff --git a/components/rgbd-sources/src/source.cpp b/components/rgbd-sources/src/source.cpp
index 384978e7f3431847806253febbe37b149aca31aa..a71ea24e9ed6106425bf88f3726f226416a39e76 100644
--- a/components/rgbd-sources/src/source.cpp
+++ b/components/rgbd-sources/src/source.cpp
@@ -2,7 +2,7 @@
 #include <ftl/rgbd/source.hpp>
 #include <ftl/threads.hpp>
 
-#include "sources/net/net.hpp"
+//#include "sources/net/net.hpp"
 #include "sources/stereovideo/stereovideo.hpp"
 #include "sources/image/image.hpp"
 #include "sources/middlebury/middlebury_source.hpp"
@@ -12,7 +12,7 @@
 #include "sources/snapshot/snapshot_source.hpp"
 #endif
 
-#include "sources/ftlfile/file_source.hpp"
+//#include "sources/ftlfile/file_source.hpp"
 
 #ifdef HAVE_REALSENSE
 #include "sources/realsense/realsense_source.hpp"
@@ -25,12 +25,12 @@ using ftl::rgbd::Source;
 using ftl::Configurable;
 using std::string;
 using ftl::rgbd::detail::StereoVideoSource;
-using ftl::rgbd::detail::NetSource;
+//using ftl::rgbd::detail::NetSource;
 using ftl::rgbd::detail::ImageSource;
 using ftl::rgbd::detail::MiddleburySource;
 using ftl::rgbd::capability_t;
 using ftl::codecs::Channel;
-using ftl::rgbd::detail::FileSource;
+//using ftl::rgbd::detail::FileSource;
 using ftl::rgbd::Camera;
 using ftl::rgbd::RawCallback;
 using ftl::rgbd::FrameCallback;
@@ -118,9 +118,11 @@ ftl::rgbd::detail::Source *Source::_createFileImpl(const ftl::URI &uri) {
 		string ext = path.substr(eix+1);
 
 		if (ext == "ftl") {
-			ftl::rgbd::Player *reader = __createReader(path);
-			LOG(INFO) << "Playing track: " << uri.getFragment();
-			return new FileSource(this, reader, std::stoi(uri.getFragment()));
+			//ftl::rgbd::Player *reader = __createReader(path);
+			//LOG(INFO) << "Playing track: " << uri.getFragment();
+			//return new FileSource(this, reader, std::stoi(uri.getFragment()));
+			LOG(FATAL) << "File sources not supported";
+			return nullptr;
 		} else if (ext == "png" || ext == "jpg") {
 			return new ImageSource(this, path);
 		} else if (ext == "mp4") {
@@ -144,7 +146,7 @@ ftl::rgbd::detail::Source *Source::_createFileImpl(const ftl::URI &uri) {
 	return nullptr;
 }
 
-ftl::rgbd::Player *Source::__createReader(const std::string &path) {
+/*ftl::rgbd::Player *Source::__createReader(const std::string &path) {
 	if (readers__.find(path) != readers__.end()) {
 		return readers__[path];
 	}
@@ -158,10 +160,12 @@ ftl::rgbd::Player *Source::__createReader(const std::string &path) {
 	readers__[path] = r;
 	r->begin();
 	return r;
-}
+}*/
 
 ftl::rgbd::detail::Source *Source::_createNetImpl(const ftl::URI &uri) {
-	return new NetSource(this);
+	LOG(FATAL) << "Net sources no longer supported";
+	//return new NetSource(this);
+	return nullptr;
 }
 
 ftl::rgbd::detail::Source *Source::_createDeviceImpl(const ftl::URI &uri) {
@@ -301,6 +305,7 @@ Camera Camera::scaled(int width, int height) const {
 }
 
 void Source::notify(int64_t ts, ftl::rgbd::Frame &f) {
+	//if (impl_) f.setOrigin(&impl_->state_);
 	if (callback_) callback_(ts, f);
 }
 
@@ -309,13 +314,13 @@ void Source::inject(const Eigen::Matrix4d &pose) {
 	ftl::codecs::Packet pkt;
 
 	spkt.timestamp = impl_->timestamp_;
-	spkt.channel_count = 0;
+	spkt.frame_number = 0;
 	spkt.channel = Channel::Pose;
 	spkt.streamID = 0;
 	pkt.codec = ftl::codecs::codec_t::MSGPACK;
 	pkt.definition = ftl::codecs::definition_t::Any;
-	pkt.block_number = 0;
-	pkt.block_total = 1;
+	pkt.bitrate = 0;
+	pkt.frame_count = 1;
 	pkt.flags = 0;
 
 	std::vector<double> data(pose.data(), pose.data() + 4*4*sizeof(double));
diff --git a/components/rgbd-sources/src/sources/ftlfile/file_source.cpp b/components/rgbd-sources/src/sources/ftlfile/file_source.cpp
index 01b602c207d6cfa7192a7534890e913655ee4286..f0ba1e010ef00e880a4ff4a532dfd310673c275c 100644
--- a/components/rgbd-sources/src/sources/ftlfile/file_source.cpp
+++ b/components/rgbd-sources/src/sources/ftlfile/file_source.cpp
@@ -76,7 +76,7 @@ FileSource::FileSource(ftl::rgbd::Source *s, ftl::rgbd::Player *r, int sid) : ft
 
 		// TODO: Check I-Frames for H264
 		if (pkt.codec == codec_t::HEVC) {
-			if (ftl::codecs::hevc::isIFrame(pkt.data)) _removeChannel(spkt.channel);
+			if (ftl::codecs::hevc::isIFrame(pkt.data.data(), pkt.data.size())) _removeChannel(spkt.channel);
 		}
 		cache_[cache_write_].emplace_back();
 		auto &c = cache_[cache_write_].back();
@@ -178,6 +178,7 @@ bool FileSource::compute(int n, int b) {
 	int lastc = 0;
 
 	frame_.reset();
+	frame_.setOrigin(&state_);
 
 	// Decide which channels to decode
 	decode_channels_ = Channel::Colour;
diff --git a/components/rgbd-sources/src/sources/ftlfile/file_source.hpp b/components/rgbd-sources/src/sources/ftlfile/file_source.hpp
index 839121f3afdde383d73635428cd172a189d7af47..9aa11b929f1d87b486064ec4337de3b48f0d1d70 100644
--- a/components/rgbd-sources/src/sources/ftlfile/file_source.hpp
+++ b/components/rgbd-sources/src/sources/ftlfile/file_source.hpp
@@ -46,8 +46,8 @@ class FileSource : public detail::Source {
 	bool freeze_;
 	bool have_frozen_;
 
-	ftl::codecs::Channels decode_channels_;
-	ftl::codecs::Channels available_channels_;
+	ftl::codecs::Channels<0> decode_channels_;
+	ftl::codecs::Channels<0> available_channels_;
 
 	void _processCalibration(ftl::codecs::Packet &pkt);
 	void _processPose(ftl::codecs::Packet &pkt);
diff --git a/components/rgbd-sources/src/sources/net/net.cpp b/components/rgbd-sources/src/sources/net/net.cpp
index 4526470083962f04e16d12c69cdb467bd69732de..57b494032afa0331ad21bcb3ac4108df114dbf9b 100644
--- a/components/rgbd-sources/src/sources/net/net.cpp
+++ b/components/rgbd-sources/src/sources/net/net.cpp
@@ -393,6 +393,7 @@ void NetSource::_completeFrame(NetFrame &frame, int64_t latency) {
 		adaptive_ = abr_.selectBitrate(frame);
 
 		frame_.reset();
+		frame_.setOrigin(&state_);
 		cv::cuda::swap(frame_.create<GpuMat>(Channel::Colour), frame.channel[0]);
 
 		if (host_->getChannel() != Channel::None)
@@ -406,6 +407,7 @@ void NetSource::_completeFrame(NetFrame &frame, int64_t latency) {
 }
 
 void NetSource::setPose(const Eigen::Matrix4d &pose) {
+	ftl::rgbd::detail::Source::setPose(pose);
 	if (!active_) return;
 
 	vector<unsigned char> vec((unsigned char*)pose.data(), (unsigned char*)(pose.data()+(pose.size())));
diff --git a/components/rgbd-sources/src/sources/stereovideo/calibrate.cpp b/components/rgbd-sources/src/sources/stereovideo/calibrate.cpp
index b2e1886bbcc0e75750099fd48f6cade7b48c1f1d..fd3b9ea917e8506a9a58acaa8ecabaff7eab2ed0 100644
--- a/components/rgbd-sources/src/sources/stereovideo/calibrate.cpp
+++ b/components/rgbd-sources/src/sources/stereovideo/calibrate.cpp
@@ -43,138 +43,243 @@ using cv::Scalar;
 using std::string;
 using std::vector;
 
-Calibrate::Calibrate(nlohmann::json &config, cv::Size image_size, cv::cuda::Stream &stream) : ftl::Configurable(config) {
-	if (!_loadCalibration()) {
-		throw ftl::exception("Loading calibration failed");
-	}
-	_calculateRectificationParameters(image_size);
+////////////////////////////////////////////////////////////////////////////////
+
+static bool isValidTranslationForRectification(const Mat t) {
+	if (t.type() != CV_64F)				{ return false; }
+	if (t.channels() != 1) 				{ return false; }
+	if (t.size() != Size(1, 3))			{ return false; }
+	if (cv::norm(t, cv::NORM_L2) == 0)	{ return false; }
+	return true;
+}
 
-	LOG(INFO) << "Calibration loaded from file";
-	rectify_ = value("rectify", true);;
+static bool isValidRotationMatrix(const Mat M) {
+	if (M.type() != CV_64F)				{ return false; }
+	if (M.channels() != 1) 				{ return false; }
+	if (M.size() != Size(3, 3))			{ return false; }
 
-	this->on("use_intrinsics", [this](const ftl::config::Event &e) {
-		rectify_ = value("rectify", true);
-	});
+	if (abs(cv::determinant(M) - 1.0) > 0.00001)
+										{ return false; } 
+
+	// accuracy problems 
+	// rotation matrix is orthogonal: M.T * M == M * M.T == I
+	//if (cv::countNonZero((M.t() * M) != Mat::eye(Size(3, 3), CV_64FC1)) != 0)
+	//									{ return false; }
+	
+	return true;
 }
 
-bool Calibrate::_loadCalibration() {
-	FileStorage fs;
+static bool isValidPose(const Mat M) {
+	if (M.size() != Size(4, 4))			{ return false; }
+	// check last row: 0 0 0 1
+	return isValidRotationMatrix(M(cv::Rect(0 , 0, 3, 3)));
+}
 
-	// read intrinsic parameters
-	auto ifile = ftl::locateFile(value("intrinsics", std::string("intrinsics.yml")));
-	if (ifile) {
-		fs.open((*ifile).c_str(), FileStorage::READ);
-		if (!fs.isOpened()) {
-			LOG(WARNING) << "Could not open intrinsics file";
-			return false;
-		}
+static bool isValidCamera(const Mat M) {
+	if (M.type() != CV_64F)				{ return false; }
+	if (M.channels() != 1)				{ return false; }
+	if (M.size() != Size(3, 3))			{ return false; }
+	// TODO: last row should be (0 0 0 1) ...
+	return true;
+}
 
-		LOG(INFO) << "Intrinsics from: " << *ifile;
-	}
-	else {
-		LOG(WARNING) << "Calibration intrinsics file not found";
-		return false;
-	}
+static Mat scaleCameraIntrinsics(Mat K, Size size_new, Size size_old) {
+	Mat S(cv::Size(3, 3), CV_64F, 0.0);
+	double scale_x = ((double) size_new.width) / ((double) size_old.width);
+	double scale_y = ((double) size_new.height) / ((double) size_old.height);
 
-	fs["K"] >> K_;
-	fs["D"] >> D_;
-	fs["resolution"] >> calib_size_;
+	S.at<double>(0, 0) = scale_x;
+	S.at<double>(1, 1) = scale_y;
+	S.at<double>(2, 2) = 1.0;
+	return S * K;
+}
 
-	if ((K_.size() != 2) || (D_.size() != 2)) {
-		LOG(ERROR) << "Not enough intrinsic paramters, expected 2";
-		return false;
-	}
+////////////////////////////////////////////////////////////////////////////////
 
-	fs.release();
+Calibrate::Calibrate(nlohmann::json &config, Size image_size, cv::cuda::Stream &stream) :
+		ftl::Configurable(config) {
+	
+	img_size_ = image_size;
+	calib_size_ = image_size;
+
+	K_ = vector<Mat>(2);
+	K_[0] = Mat::eye(Size(3, 3), CV_64FC1);
+	K_[1] = Mat::eye(Size(3, 3), CV_64FC1);
+	D_ = vector<Mat>(2);
+	D_[0] = Mat::zeros(Size(5, 1), CV_64FC1);
+	D_[1] = Mat::zeros(Size(5, 1), CV_64FC1);
+	pose_ = Mat::eye(Size(4, 4), CV_64FC1);
+	Q_ = Mat::eye(Size(4, 4), CV_64FC1);
+	Q_.at<double>(3, 2) = -1;
+	Q_.at<double>(2, 3) = 1;
+
+	setRectify(true);
+}
 
-	if (calib_size_.empty()) {
-		LOG(ERROR) << "Calibration resolution missing";
-		return false;
-	}
+Mat Calibrate::_getK(size_t idx, Size size) {
+	CHECK(idx < K_.size());
+	CHECK(!size.empty());
+	return scaleCameraIntrinsics(K_[idx], size, calib_size_);
+}
 
-	for (const Mat &K : K_) {
-		if (K.size() != Size(3, 3)) {
-			LOG(ERROR) << "Invalid intrinsic parameters";
-			return false;
-		}
-	}
-	for (const Mat &D : D_) {
-		if (D.size() != Size(5, 1)) {
-			LOG(ERROR) << "Invalid intrinsic parameters";
-			return false;
-		}
+Mat Calibrate::_getK(size_t idx) {
+	return _getK(idx, img_size_);
+}
+
+cv::Mat Calibrate::getCameraMatrixLeft(const cv::Size res) {
+	if (rectify_) {
+		return Mat(P1_, cv::Rect(0, 0, 3, 3));
+	} else {
+		return scaleCameraIntrinsics(K_[0], res, img_size_);
 	}
+}
 
-	// read extrinsic parameters
-	auto efile = ftl::locateFile(value("extrinsics", std::string("extrinsics.yml")));
-	if (efile) {
-		fs.open((*efile).c_str(), FileStorage::READ);
-		if (!fs.isOpened()) {
-			LOG(ERROR) << "Could not open extrinsics file";
-			return false;
-		}
+cv::Mat Calibrate::getCameraMatrixRight(const cv::Size res) {
+	if (rectify_) {
+		return Mat(P2_, cv::Rect(0, 0, 3, 3));
+	} else {
+		return scaleCameraIntrinsics(K_[1], res, img_size_);
+	}
+}
 
-		LOG(INFO) << "Extrinsics from: " << *efile;
+bool Calibrate::setRectify(bool enabled) {
+	if (t_.empty() || R_.empty()) { enabled = false; }
+	if (enabled) { 
+		rectify_ = calculateRectificationParameters(); 
 	}
 	else {
-		LOG(ERROR) << "Calibration extrinsics file not found";
-		return false;
+		rectify_ = false;
 	}
+	return rectify_;
+}
 
-	fs["R"] >> R_;
-	fs["T"] >> T_;
-	fs["pose"] >> pose_;
+bool Calibrate::setIntrinsics(const Size size, const vector<Mat> K, const vector<Mat> D) {
+	if (size.empty() || size.width <= 0 || size.height <= 0) { return false; }
+	if ((K.size() != 2) || (D.size() != 2)) { return false; }
+	for (const auto k : K) { if (!isValidCamera(k)) { return false; }}
+	for (const auto d : D) { if (d.size() != Size(5, 1)) { return false; }}
+
+	calib_size_ = size;
+	K[0].copyTo(K_[0]);
+	K[1].copyTo(K_[1]);
+	D[0].copyTo(D_[0]);
+	D[1].copyTo(D_[1]);
+	return true;
+}
 
-	if (pose_.size() != Size(4, 4)) {
-		LOG(ERROR) << "Pose not in calibration (using identity)";
-		// TODO: return false (raises exception in constructor)
-		//		 use config option to make pose optional (and not return false)
+bool Calibrate::setExtrinsics(const Mat R, const Mat t) {
+	if (!isValidRotationMatrix(R) ||
+		!isValidTranslationForRectification(t)) { return false; }
+	
+	R.copyTo(R_);
+	t.copyTo(t_);
+	return true;
+}
 
-		pose_ = Mat::eye(Size(4, 4), CV_64FC1);
-	}
+bool Calibrate::setPose(const Mat P) {
+	if (!isValidPose(P)) { return false; }
+	P.copyTo(pose_);
+	return true;
+}
 
-	if ((R_.size() != Size(3, 3)) ||
-		(T_.size() != Size(1, 3))) {
+bool Calibrate::loadCalibration(const string fname) {
+	FileStorage fs;
 
-		LOG(ERROR) << "Invalid extrinsic parameters";
+	fs.open((fname).c_str(), FileStorage::READ);
+	if (!fs.isOpened()) {
+		LOG(WARNING) << "Could not open calibration file";
 		return false;
 	}
+
+	Size calib_size;
+	vector<Mat> K;
+	vector<Mat> D;
+	Mat R;
+	Mat t;
+	Mat pose;
+
+	fs["resolution"] >> calib_size;
+	fs["K"] >> K;
+	fs["D"] >> D;
+	fs["R"] >> R;
+	fs["t"] >> t;
+	fs["P"] >> pose;
 	fs.release();
 
+	if (calib_size.empty()) {
+		LOG(ERROR) << "calibration resolution missing in calibration file";
+		return false;
+	}
+	if (!setIntrinsics(calib_size, K, D)) {
+		LOG(ERROR) << "invalid intrinsics in calibration file";
+		return false;
+	}
+	if (!setExtrinsics(R, t)) {
+		LOG(ERROR) << "invalid extrinsics in calibration file";
+		return false;
+	}
+	if (!setPose(pose)) {
+		LOG(ERROR) << "invalid pose in calibration file";
+		return false; // TODO: allow missing pose? (config option)
+	}
+
+	LOG(INFO) << "calibration loaded from: " << fname;
+	return true;
+}
+
+bool Calibrate::writeCalibration(	const string fname, const Size size,
+									const vector<Mat> K, const vector<Mat> D, 
+									const Mat R, const Mat t, const Mat pose) {
+	
+	cv::FileStorage fs(fname, cv::FileStorage::WRITE);
+	if (!fs.isOpened()) { return false; }
+
+	fs	<< "resolution" << size
+		<< "K" << K
+		<< "D" << D
+		<< "R" << R
+		<< "t" << t
+		<< "P" << pose
+	;
+	
+	fs.release();
 	return true;
 }
 
-void Calibrate::_calculateRectificationParameters(Size img_size) {
+bool Calibrate::saveCalibration(const string fname) {
+	// note: never write rectified parameters!
+	return writeCalibration(fname, calib_size_, K_, D_, R_, t_, pose_);
+}
+
+bool Calibrate::calculateRectificationParameters() {
 	
-	img_size_ = img_size;
-	Mat K1 = _getK(0, img_size);
+	Mat K1 = _getK(0, img_size_);
 	Mat D1 = D_[0];
-	Mat K2 = _getK(1, img_size);
+	Mat K2 = _getK(1, img_size_);
 	Mat D2 = D_[1];
 	double alpha = value("alpha", 0.0);
 
-	cv::stereoRectify(	K1, D1, K2, D2,
-						img_size, R_, T_,
-						R1_, R2_, P1_, P2_, Q_, 0, alpha);
-	
-	// TODO use fixed point maps for CPU (gpu remap() requires floating point)
-	initUndistortRectifyMap(K1, D1, R1_, P1_, img_size, CV_32FC1, map1_.first, map2_.first);
-	initUndistortRectifyMap(K2, D2, R2_, P2_, img_size, CV_32FC1, map1_.second, map2_.second);
+	try {
+		cv::stereoRectify(	K1, D1, K2, D2,
+							img_size_, R_, t_,
+							R1_, R2_, P1_, P2_, Q_, 0, alpha);
+		
+		// TODO use fixed point maps for CPU (gpu remap() requires floating point)
+		initUndistortRectifyMap(K1, D1, R1_, P1_, img_size_, CV_32FC1, map1_.first, map2_.first);
+		initUndistortRectifyMap(K2, D2, R2_, P2_, img_size_, CV_32FC1, map1_.second, map2_.second);
+	}
+	catch (cv::Exception ex) {
+		LOG(ERROR) << ex.what();
+		return false;
+	}
 
 	// CHECK Is this thread safe!!!!
 	map1_gpu_.first.upload(map1_.first);
 	map1_gpu_.second.upload(map1_.second);
 	map2_gpu_.first.upload(map2_.first);
 	map2_gpu_.second.upload(map2_.second);
-}
-
-void Calibrate::updateCalibration(const ftl::rgbd::Camera &p) {
-	Q_.at<double>(3, 2) = 1.0 / p.baseline;
-	Q_.at<double>(2, 3) = p.fx;
-	Q_.at<double>(0, 3) = p.cx;
-	Q_.at<double>(1, 3) = p.cy;
 
-	// FIXME:(Nick) Update camera matrix also...
+	return true;
 }
 
 void Calibrate::rectifyStereo(GpuMat &l, GpuMat &r, Stream &stream) {
@@ -196,44 +301,3 @@ void Calibrate::rectifyStereo(cv::Mat &l, cv::Mat &r) {
 	cv::remap(l, l, map1_.first, map2_.first, cv::INTER_LINEAR, 0, cv::Scalar());
 	cv::remap(r, r, map1_.second, map2_.second, cv::INTER_LINEAR, 0, cv::Scalar());
 }
-
-static Mat scaleCameraIntrinsics(Mat K, Size size_new, Size size_old) {
-	Mat S(cv::Size(3, 3), CV_64F, 0.0);
-	double scale_x = ((double) size_new.width) / ((double) size_old.width);
-	double scale_y = ((double) size_new.height) / ((double) size_old.height);
-
-	S.at<double>(0, 0) = scale_x;
-	S.at<double>(1, 1) = scale_y;
-	S.at<double>(2, 2) = 1.0;
-	return S * K;
-}
-
-Mat Calibrate::_getK(size_t idx, Size size) {
-	CHECK(idx < K_.size());
-	CHECK(!size.empty());
-	return scaleCameraIntrinsics(K_[idx], size, calib_size_);
-}
-
-Mat Calibrate::_getK(size_t idx) {
-	return _getK(idx, img_size_);
-}
-
-cv::Mat Calibrate::getCameraMatrixLeft(const cv::Size res) {
-	Mat M;
-	if (rectify_) {
-		M = Mat(P1_, cv::Rect(0, 0, 3, 3));
-	} else {
-		M = K_[0];
-	}
-	return scaleCameraIntrinsics(M, res, img_size_);
-}
-
-cv::Mat Calibrate::getCameraMatrixRight(const cv::Size res) {
-	Mat M;
-	if (rectify_) {
-		M = Mat(P2_, cv::Rect(0, 0, 3, 3));
-	} else {
-		M = K_[1];
-	}
-	return scaleCameraIntrinsics(M, res, img_size_);
-}
diff --git a/components/rgbd-sources/src/sources/stereovideo/calibrate.hpp b/components/rgbd-sources/src/sources/stereovideo/calibrate.hpp
index 82a06e756591ced2fe11535011b7d422320b7597..ed63f125ec49d22767dc1ea7a1c665503147ebe3 100644
--- a/components/rgbd-sources/src/sources/stereovideo/calibrate.hpp
+++ b/components/rgbd-sources/src/sources/stereovideo/calibrate.hpp
@@ -29,11 +29,13 @@ class Calibrate : public ftl::Configurable {
 	public:
 	Calibrate(nlohmann::json &config, cv::Size image_size, cv::cuda::Stream &stream);
 
-	/* @brief	Rectify and undistort stereo pair images (GPU)
+	/**
+	 * @brief	Rectify and undistort stereo pair images (GPU)
 	 */
 	void rectifyStereo(cv::cuda::GpuMat &l, cv::cuda::GpuMat &r, cv::cuda::Stream &stream);
 
-	/* @brief	Rectify and undistort stereo pair images (CPU)
+	/**
+	 * @brief	Rectify and undistort stereo pair images (CPU)
 	 * @todo	Uses same rectification maps as GPU version, according to OpenCV
 	 * 			documentation for remap(), fixed point versions faster for CPU
 	 */
@@ -41,10 +43,13 @@ class Calibrate : public ftl::Configurable {
 
 	void updateCalibration(const ftl::rgbd::Camera &p);
 	
-	/* @brief Get disparity to depth matrix
+	/**
+	 * @brief Get disparity to depth matrix
 	 *
-	 * 2020/01/15:	Not used, StereoVideoSource creates a Camera object which
-	 * 				is used to calculate depth from disparity (disp2depth.cu)
+	 * 2020/01/15:	StereoVideoSource creates a Camera object which is used to
+	 * 				calculate depth from disparity (disp2depth.cu). Seems to be
+	 * 				used only in StereoVideoSource to get doff and baseline
+	 * 				parameter values in updateParameters()
 	 */
 	const cv::Mat &getQ() const { return Q_; }
 
@@ -54,39 +59,100 @@ class Calibrate : public ftl::Configurable {
 	cv::Mat getCameraMatrixLeft(const cv::Size res);
 	cv::Mat getCameraMatrixRight(const cv::Size res);
 
-	/* @brief	Get camera pose from calibration
+	/**
+	 * @brief	Get camera pose from calibration
 	 */
-	cv::Mat getPose() { return pose_; };
+	const cv::Mat &getPose() const { return pose_; };
 	
-	/* @brief	Enable/disable recitification. If disabled, instance returns
+	/**
+	 * @brief	Enable/disable recitification. If disabled, instance returns
 	 *			original camera intrinsic parameters (getCameraMatrixLeft() and
-				getCameraMatrixRight() methods). When enabled (default), those
-				methods return camera parameters for rectified images.
+	 *			getCameraMatrixRight() methods). When enabled (default), those
+	 *			methods return camera parameters for rectified images. Does not
+	 *			enable rectification, if valid parameters are missing.
 	 * @param	Rectification on/off
+	 * @returns	Status after call
 	 */
-	void setRectify(bool enabled) { rectify_ = enabled; }
+	bool setRectify(bool enabled);
+
+	/**
+	 * @brief	Set intrinsic parameters for both cameras.
+	 * 
+	 * @param	size	calibration size
+	 * @param	K		2 camera matricies (3x3)
+	 * @param	D 		2 distortion parameters (5x1)
+	 * @returns	true if valid parameters
+	 */
+	bool setIntrinsics(const cv::Size size, const std::vector<cv::Mat> K, const std::vector<cv::Mat> D);
+
+	/**
+	 * @brief	Set extrinsic parameters.
+	 * 
+	 * @param	R	Rotation matrix (3x3) from left to right camera
+	 * @param	t	Translation vector (1x3) from left to right camera
+	 * @returns	true if valid parameters
+	 */
+	bool setExtrinsics(const cv::Mat R, const cv::Mat t);
+
+	/**
+	 * @brief	Set pose
+	 * @param	pose	Pose for left camera
+	 * @returns	true if valid pose
+	 */
+	bool setPose(const cv::Mat P);
+
+	/**
+	 * @brief	Calculate rectification parameters and maps. Can fail if
+	 * 			calibration parameters are invalid.
+	 * @returns	true if successful
+	 */
+	bool calculateRectificationParameters();
+
+	/**
+	 * @brief	Load calibration from file
+	 * @param	fname	File name
+	 */
+	bool loadCalibration(const std::string fname);
+
+	/**
+	 * @brief	Write calibration parameters to file
+	 * 
+	 * Assumes two cameras and intrinsic calibration parameters have the same
+	 * resolution.
+	 * 
+	 * @todo	Validate loaded values
+	 * 
+	 * @param	fname file name
+	 * @param	size calibration resolution (intrinsic parameters)
+	 * @param	K intrinsic matrices
+	 * @param	D distortion coefficients
+	 * @param	R rotation from first camera to second
+	 * @param	t translation from first camera to second
+	 * @param	pose first camera's pose 
+	 */
+	static bool writeCalibration(std::string fname,
+								cv::Size size,
+								std::vector<cv::Mat> K, std::vector<cv::Mat> D, 
+								cv::Mat R, cv::Mat t,
+								cv::Mat pose);
+
+	/*	@brief	Save current calibration to file
+	 *	@param	File name
+	 */
+	bool saveCalibration(const std::string fname);
 
 private:
 	// rectification enabled/disabled
 	bool rectify_;
 
-	/* @brief	Get intrinsic matrix saved in calibration.
+	/**
+	 * @brief	Get intrinsic matrix saved in calibration.
 	 * @param	Camera index (0 left, 1 right)
 	 * @param	Resolution
 	 */
 	cv::Mat _getK(size_t idx, cv::Size size);
 	cv::Mat _getK(size_t idx);
 
-	/* @brief	Calculate rectification parameters and maps
-	 * @param	Camera resolution
-	 */
-	void _calculateRectificationParameters(cv::Size img_size);
-
-	/* @brief	Load calibration from file
-	 * @todo	File names as arguments
-	 */
-	bool _loadCalibration();
-	
 	// calibration resolution (loaded from file by _loadCalibration)
 	cv::Size calib_size_;
 	// camera resolution (set by _calculateRecitificationParameters)
@@ -98,12 +164,6 @@ private:
 	std::pair<cv::cuda::GpuMat, cv::cuda::GpuMat> map1_gpu_;
 	std::pair<cv::cuda::GpuMat, cv::cuda::GpuMat> map2_gpu_;
 
-	// transformation from left to right camera: R_ and T_
-	cv::Mat R_;
-	cv::Mat T_;
-	// pose for left camera
-	cv::Mat pose_;
-
 	// parameters for rectification, see cv::stereoRectify() documentation
 	cv::Mat R1_;
 	cv::Mat P1_;
@@ -116,6 +176,12 @@ private:
 	// intrinsic parameters and distortion coefficients
 	std::vector<cv::Mat> K_;
 	std::vector<cv::Mat> D_;
+
+	// transformation from left to right camera: R_ and T_
+	cv::Mat R_;
+	cv::Mat t_;
+	// pose for left camera
+	cv::Mat pose_;
 };
 
 }
diff --git a/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp b/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp
index 330a5035c46fd38cc4f1ea9ccb6b61bbc12828d8..ccb3cada41924f3ab9fd0e0c12ba62aa48d02375 100644
--- a/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp
+++ b/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp
@@ -1,5 +1,8 @@
 #include <loguru.hpp>
 
+#include <Eigen/Eigen>
+#include <opencv2/core/eigen.hpp>
+
 #include "stereovideo.hpp"
 
 #include "ftl/configuration.hpp"
@@ -67,7 +70,7 @@ void StereoVideoSource::init(const string &file) {
 		LOG(INFO) << "Using cameras...";
 		lsrc_ = ftl::create<LocalSource>(host_, "feed");
 	}
-
+	
 	color_size_ = cv::Size(lsrc_->width(), lsrc_->height());
 	frames_ = std::vector<Frame>(2);
 
@@ -76,99 +79,133 @@ void StereoVideoSource::init(const string &file) {
 	pipeline_input_->append<ftl::operators::NVOpticalFlow>("optflow");
 	#endif
 
-	//depth_size_ = cv::Size(	host_->value("depth_width", 1280),
-	//						host_->value("depth_height", 720));
-
-	/*pipeline_depth_ = ftl::config::create<ftl::operators::Graph>(host_, "disparity");
-	depth_size_ = cv::Size(	pipeline_depth_->value("width", color_size_.width),
-							pipeline_depth_->value("height", color_size_.height));
+	calib_ = ftl::create<Calibrate>(host_, "calibration", cv::Size(lsrc_->fullWidth(), lsrc_->fullHeight()), stream_);
 
-	pipeline_depth_->append<ftl::operators::FixstarsSGM>("algorithm");
-	#ifdef HAVE_OPTFLOW
-	pipeline_depth_->append<ftl::operators::OpticalFlowTemporalSmoothing>("optflow_filter");
-	#endif
-	pipeline_depth_->append<ftl::operators::DisparityBilateralFilter>("bilateral_filter");
-	pipeline_depth_->append<ftl::operators::DisparityToDepth>("calculate_depth");
-	pipeline_depth_->append<ftl::operators::ColourChannels>("colour");  // Convert BGR to BGRA
-	pipeline_depth_->append<ftl::operators::Normals>("normals");  // Estimate surface normals
-	pipeline_depth_->append<ftl::operators::CrossSupport>("cross");
-	pipeline_depth_->append<ftl::operators::DiscontinuityMask>("discontinuity_mask");
-	pipeline_depth_->append<ftl::operators::AggreMLS>("mls");  // Perform MLS (using smoothing channel)*/
+	string fname_default = "calibration.yml";
+	auto fname_config = calib_->get<string>("calibration");
+	string fname = fname_config ? *fname_config : fname_default;
+	auto calibf = ftl::locateFile(fname);
+	if (calibf) {
+		fname = *calibf;
+		if (calib_->loadCalibration(fname)) {
+			calib_->calculateRectificationParameters();
+			calib_->setRectify(true);
+		}
+	}
+	else {
+		fname = fname_config ? *fname_config : 
+								string(FTL_LOCAL_CONFIG_ROOT) + "/"
+								+ std::string("calibration.yml");
+		
+		LOG(ERROR) << "No calibration, default path set to " + fname;
+	}
 
-	calib_ = ftl::create<Calibrate>(host_, "calibration", cv::Size(lsrc_->fullWidth(), lsrc_->fullHeight()), stream_);
+	////////////////////////////////////////////////////////////////////////////
+	// RPC callbacks to update calibration
+	// Should only be used by calibration app (interface may change)
+	// Tries to follow interface of ftl::Calibrate
 	
-	// Generate camera parameters from camera matrix
-	cv::Mat K = calib_->getCameraMatrixLeft(color_size_);
-	params_ = {
-		K.at<double>(0,0),	// Fx
-		K.at<double>(1,1),	// Fy
-		-K.at<double>(0,2),	// Cx
-		-K.at<double>(1,2),	// Cy
-		(unsigned int) lsrc_->width(),
-		(unsigned int) lsrc_->height(),
-		0.0f,	// 0m min
-		15.0f,	// 15m max
-		1.0 / calib_->getQ().at<double>(3,2), // Baseline
-		0.0f  // doffs
-	};
-	params_.doffs = -calib_->getQ().at<double>(3,3) * params_.baseline;
-
-	// Add calibration to config object
-	host_->getConfig()["focal"] = params_.fx;
-	host_->getConfig()["centre_x"] = params_.cx;
-	host_->getConfig()["centre_y"] = params_.cy;
-	host_->getConfig()["baseline"] = params_.baseline;
-	host_->getConfig()["doffs"] = params_.doffs;
+	host_->getNet()->bind("set_pose",
+		[this](std::vector<double> data){
+			if (data.size() != 16) {
+				LOG(ERROR) << "invalid pose received (wrong size)";
+				return false;
+			}
+
+			cv::Mat M = cv::Mat(data).reshape(1, 4).t();
+			if (!calib_->setPose(M)) {
+				LOG(ERROR) << "invalid pose received (bad value)";
+				return false;
+			}
+
+			return true;
+	});
 
-	// TODO: remove (not used, fx/fy/baseline/.. do not change)
-	//		 in case they are modified, update using Calibrate
-	//		 (requires new method)
+	host_->getNet()->bind("set_intrinsics",
+		[this](	std::vector<int> size,
+				std::vector<double> camera_l, std::vector<double> d_left,
+				std::vector<double> camera_r, std::vector<double> d_right) {
+		if ((size.size() != 2) || (camera_l.size() != 9) || (camera_r.size() != 9)) {
+			LOG(ERROR) << "bad intrinsic parameters (wrong size)";
+			return false;
+		}
+		cv::Size calib_size(size[0], size[1]);
+		cv::Mat K_l = cv::Mat(camera_l).reshape(1, 3).t();
+		cv::Mat K_r = cv::Mat(camera_r).reshape(1, 3).t();
+		cv::Mat D_l = cv::Mat(D_l);
+		cv::Mat D_r = cv::Mat(D_r);
+		
+		if (!calib_->setIntrinsics(calib_size, {K_l, K_r}, {D_l, D_r})) {
+			LOG(ERROR) << "bad intrinsic parameters (bad values)";
+			return false;
+		}
+		return true;
+	});
 
-	host_->on("baseline", [this](const ftl::config::Event &e) {
-		params_.baseline = host_->value("baseline", params_.baseline);
-		UNIQUE_LOCK(host_->mutex(), lk);
-		calib_->updateCalibration(params_);
+	host_->getNet()->bind("set_extrinsics",
+		[this](std::vector<double> data_rvec, std::vector<double> data_tvec){
+			if ((data_rvec.size() != 3) || (data_tvec.size() != 3)) {
+				LOG(ERROR) << "invalid extrinsic parameters received (wrong size)";
+				return false;
+			}
+
+			cv::Mat R;
+			cv::Rodrigues(data_rvec, R);
+			cv::Mat t(data_tvec);
+			
+			if (!calib_->setExtrinsics(R, t)) {
+				LOG(ERROR) << "invalid extrinsic parameters (bad values)";
+				return false;
+			}
+			return true;
 	});
 
-	host_->on("focal", [this](const ftl::config::Event &e) {
-		params_.fx = host_->value("focal", params_.fx);
-		params_.fy = params_.fx;
-		UNIQUE_LOCK(host_->mutex(), lk);
-		calib_->updateCalibration(params_);
+	host_->getNet()->bind("save_calibration", 
+		[this, fname](){
+			return calib_->saveCalibration(fname);
 	});
-	//
 
-	host_->on("doffs", [this](const ftl::config::Event &e) {
-		params_.doffs = host_->value("doffs", params_.doffs);
+	host_->getNet()->bind("use_rectify", 
+		[this](bool enable){
+			bool retval = enable && calib_->setRectify(enable);
+			updateParameters();
+			return retval;
 	});
-	
 
-	// left and right masks (areas outside rectified images)
-	// TODO: remove mask
-	cv::cuda::GpuMat mask_r_gpu(lsrc_->height(), lsrc_->width(), CV_8U, 255);
-	cv::cuda::GpuMat mask_l_gpu(lsrc_->height(), lsrc_->width(), CV_8U, 255);
-	calib_->rectifyStereo(mask_l_gpu, mask_r_gpu, stream_);
-	stream_.waitForCompletion();
-	cv::Mat mask_l;
-	mask_l_gpu.download(mask_l);
-	mask_l_ = (mask_l == 0);
-	
+	////////////////////////////////////////////////////////////////////////////
 
+	// Generate camera parameters from camera matrix
+	updateParameters();
+	
 	LOG(INFO) << "StereoVideo source ready...";
 	ready_ = true;
+
+	state_.set("name", host_->value("name", host_->getID()));
 }
 
 ftl::rgbd::Camera StereoVideoSource::parameters(Channel chan) {
-	cv::Mat K;
-	
 	if (chan == Channel::Right) {
-		K = calib_->getCameraMatrixRight(color_size_);
+		return state_.getRight();
 	} else {
-		K = calib_->getCameraMatrixLeft(color_size_);
+		return state_.getLeft();
 	}
+}
+
+void StereoVideoSource::updateParameters() {
+	Eigen::Matrix4d pose;
+	cv::cv2eigen(calib_->getPose(), pose);
+	setPose(pose);
+
+	cv::Mat K;
+	
+	// same for left and right
+	double baseline = calib_->getQ().at<double>(3,2);
+	double doff =  -calib_->getQ().at<double>(3,3) * baseline;
+
+	// left
 
-	// TODO: remove hardcoded values (min/max), move to Calibrate?
-	ftl::rgbd::Camera params = {
+	K = calib_->getCameraMatrixLeft(color_size_);
+	state_.getLeft() = {
 		K.at<double>(0,0),	// Fx
 		K.at<double>(1,1),	// Fy
 		-K.at<double>(0,2),	// Cx
@@ -177,11 +214,31 @@ ftl::rgbd::Camera StereoVideoSource::parameters(Channel chan) {
 		(unsigned int) color_size_.height,
 		0.0f,	// 0m min
 		15.0f,	// 15m max
-		1.0 / calib_->getQ().at<double>(3,2), // Baseline
-		0.0f  // doffs
+		1.0 / baseline, // Baseline
+		doff
 	};
 	
-	return params;
+	host_->getConfig()["focal"] = params_.fx;
+	host_->getConfig()["centre_x"] = params_.cx;
+	host_->getConfig()["centre_y"] = params_.cy;
+	host_->getConfig()["baseline"] = params_.baseline;
+	host_->getConfig()["doffs"] = params_.doffs;
+
+	// right
+
+	K = calib_->getCameraMatrixRight(color_size_);
+	state_.getRight() = {
+		K.at<double>(0,0),	// Fx
+		K.at<double>(1,1),	// Fy
+		-K.at<double>(0,2),	// Cx
+		-K.at<double>(1,2),	// Cy
+		(unsigned int) color_size_.width,
+		(unsigned int) color_size_.height,
+		0.0f,	// 0m min
+		15.0f,	// 15m max
+		1.0 / baseline, // Baseline
+		doff
+	};
 }
 
 bool StereoVideoSource::capture(int64_t ts) {
@@ -193,6 +250,7 @@ bool StereoVideoSource::capture(int64_t ts) {
 bool StereoVideoSource::retrieve() {
 	auto &frame = frames_[0];
 	frame.reset();
+	frame.setOrigin(&state_);
 	auto &left = frame.create<cv::cuda::GpuMat>(Channel::Left);
 	auto &right = frame.create<cv::cuda::GpuMat>(Channel::Right);
 	cv::cuda::GpuMat dummy;
@@ -202,7 +260,7 @@ bool StereoVideoSource::retrieve() {
 
 	//LOG(INFO) << "Channel size: " << hres.size();
 
-	pipeline_input_->apply(frame, frame, host_, cv::cuda::StreamAccessor::getStream(stream2_));
+	pipeline_input_->apply(frame, frame, cv::cuda::StreamAccessor::getStream(stream2_));
 	stream2_.waitForCompletion();
 	
 	return true;
@@ -232,43 +290,6 @@ bool StereoVideoSource::compute(int n, int b) {
 	//stream_.waitForCompletion();
 	host_->notify(timestamp_, frame);
 
-	/*if (chan == Channel::Depth) {
-		// stereo algorithms assume input same size as output
-		bool resize = (depth_size_ != color_size_);
-
-		cv::cuda::GpuMat& left = frame.get<cv::cuda::GpuMat>(Channel::Left);
-		cv::cuda::GpuMat& right = frame.get<cv::cuda::GpuMat>(Channel::Right);
-
-		if (left.empty() || right.empty()) {
-			return false;
-		}
-
-		if (resize) {
-			cv::cuda::swap(fullres_left_, left);
-			cv::cuda::swap(fullres_right_, right);
-			cv::cuda::resize(fullres_left_, left, depth_size_, 0, 0, cv::INTER_CUBIC, stream_);
-			cv::cuda::resize(fullres_right_, right, depth_size_, 0, 0, cv::INTER_CUBIC, stream_);
-		}
-
-		pipeline_depth_->apply(frame, frame, host_, cv::cuda::StreamAccessor::getStream(stream_));
-		stream_.waitForCompletion();
-		
-		if (resize) {
-			cv::cuda::swap(fullres_left_, left);
-			cv::cuda::swap(fullres_right_, right);
-		}
-
-		host_->notify(timestamp_, frame);
-
-	} else if (chan == Channel::Right) {
-		stream_.waitForCompletion();
-		host_->notify(timestamp_, frame);
-	
-	} else {
-		stream_.waitForCompletion();
-		host_->notify(timestamp_, frame);
-	}*/
-
 	return true;
 }
 
diff --git a/components/rgbd-sources/src/sources/stereovideo/stereovideo.hpp b/components/rgbd-sources/src/sources/stereovideo/stereovideo.hpp
index 9532e618889e78da51e1e152673195000e93be7f..4b6b60e63a522c8031f5cc386fe6211eba064b9f 100644
--- a/components/rgbd-sources/src/sources/stereovideo/stereovideo.hpp
+++ b/components/rgbd-sources/src/sources/stereovideo/stereovideo.hpp
@@ -30,9 +30,12 @@ class StereoVideoSource : public detail::Source {
 	bool retrieve();
 	bool compute(int n, int b);
 	bool isReady();
+
 	Camera parameters(ftl::codecs::Channel chan) override;
 
 	private:
+	void updateParameters();
+
 	LocalSource *lsrc_;
 	Calibrate *calib_;
 
diff --git a/components/rgbd-sources/src/sources/virtual/virtual.cpp b/components/rgbd-sources/src/sources/virtual/virtual.cpp
index 0827c0ffdf81af80565a07dbd4ee379c959700bc..1580750afb047150e7f2fe2970aef80661d6fff9 100644
--- a/components/rgbd-sources/src/sources/virtual/virtual.cpp
+++ b/components/rgbd-sources/src/sources/virtual/virtual.cpp
@@ -81,6 +81,7 @@ class VirtualImpl : public ftl::rgbd::detail::Source {
 	bool compute(int n, int b) override {
 		if (callback) {
 			frame_.reset();
+			frame_.setOrigin(&state_);
 			bool goodFrame = false;
 
 			try {
diff --git a/components/rgbd-sources/src/streamer.cpp b/components/rgbd-sources/src/streamer.cpp
index 75165ffd1d49dc89a268f9a1c3bb92575b644980..e6c08e5b53feaba743dcbb3816cb16fe7f9ad7ed 100644
--- a/components/rgbd-sources/src/streamer.cpp
+++ b/components/rgbd-sources/src/streamer.cpp
@@ -43,12 +43,11 @@ Streamer::Streamer(nlohmann::json &config, Universe *net)
 	//last_dropped_ = 0;
 	//drop_count_ = 0;
 
-	encode_mode_ = ftl::rgbd::kEncodeVideo;
+	second_channel_ = Channel::None;
 	hq_devices_ = (value("disable_hardware_encode", false)) ? device_t::Software : device_t::Any;
 	hq_codec_ = value("video_codec", ftl::codecs::codec_t::Any);
 
 	//group_.setFPS(value("fps", 20));
-	group_.setLatency(4);
 	group_.setName("NetStreamer");
 
 	compress_level_ = value("compression", 1);
@@ -116,9 +115,10 @@ Streamer::Streamer(nlohmann::json &config, Universe *net)
 	net->bind("set_channel", [this](const string &uri, Channel chan) {
 		SHARED_LOCK(mutex_,slk);
 
-		if (sources_.find(uri) != sources_.end()) {
-			sources_[uri]->src->setChannel(chan);
-		}
+		//if (sources_.find(uri) != sources_.end()) {
+		//	sources_[uri]->src->setChannel(chan);
+		//}
+		second_channel_ = chan;
 	});
 
 	//net->bind("sync_streams", [this](unsigned long long time) {
@@ -202,6 +202,7 @@ void Streamer::add(Source *src) {
 		s->lq_bitrate = value("lq_bitrate", ftl::codecs::kPresetWorst);
 
 		sources_[src->getID()] = s;
+		sourcesByNum_.push_back(s);
 
 		group_.addSource(src);
 
@@ -219,6 +220,17 @@ void Streamer::add(Source *src) {
 
 	LOG(INFO) << "Streaming: " << src->getID();
 	net_->broadcast("add_stream", src->getID());
+
+	// FIXME: Temp hack for forward compatibility.
+	net_->bind(src->getID(), [this,src](ftl::net::Peer &p, short ttime, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+		LOG(INFO) << "RECEIVED PACKET: " << spkt.timestamp;
+		_addClient(src->getID(), pkt.frame_count, 0, p.id(), src->getID());
+		if (spkt.channel != Channel::Colour && src->getChannel() != spkt.channel) {
+			LOG(INFO) << "SET CHANNEL: " << (int)spkt.channel;
+			src->setChannel(spkt.channel);
+			second_channel_ = spkt.channel;
+		}
+	});
 }
 
 void Streamer::add(ftl::rgbd::Group *grp) {
@@ -380,14 +392,14 @@ void Streamer::stop() {
 
 void Streamer::run(bool block) {
 	if (block) {
-		group_.sync([this](FrameSet &fs) -> bool {
+		group_.onFrameSet([this](FrameSet &fs) -> bool {
 			_process(fs);
 			return true;
 		});
 	} else {
 		// Create thread job for frame ticking
 		ftl::pool.push([this](int id) {
-			group_.sync([this](FrameSet &fs) -> bool {
+			group_.onFrameSet([this](FrameSet &fs) -> bool {
 				_process(fs);
 				return true;
 			});
@@ -454,29 +466,29 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) {
 
 	frame_no_ = fs.timestamp;
 
-	for (int j=0; j<fs.sources.size(); ++j) {
-		StreamSource *src = sources_[fs.sources[j]->getID()];
+	for (int j=0; j<fs.frames.size(); ++j) {
+		StreamSource *src = sourcesByNum_[j];
 		SHARED_LOCK(src->mutex,lk);
 
 		// Don't do any work in the following cases
 		if (!src) continue;
-		if (!fs.sources[j]->isReady()) continue;
+		//if (!fs.sources[j]->isReady()) continue;
 		if (src->clientCount == 0) continue;
 		//if (fs.channel1[j].empty() || (fs.sources[j]->getChannel() != ftl::rgbd::kChanNone && fs.channel2[j].empty())) continue;
-		if (!fs.frames[j].hasChannel(Channel::Colour) || !fs.frames[j].hasChannel(fs.sources[j]->getChannel())) {
-			LOG(WARNING) << "Missing required channel when streaming: " << (int)fs.sources[j]->getChannel();
+		if (!fs.frames[j].hasChannel(Channel::Colour) || !fs.frames[j].hasChannel(second_channel_)) {
+			LOG(WARNING) << "Missing required channel when streaming: " << (int)second_channel_;
 			continue;
 		}
 
-		bool hasChan2 = fs.sources[j]->getChannel() != Channel::None &&
-				fs.frames[j].hasChannel(fs.sources[j]->getChannel());
+		bool hasChan2 = second_channel_ != Channel::None &&
+				fs.frames[j].hasChannel(second_channel_);
 
 		totalclients += src->clientCount;
 
 		// Do we need to do high quality encoding?
 		if (src->hq_count > 0) {
 			
-			auto chan = fs.sources[j]->getChannel();
+			auto chan = second_channel_;
 
 			// Do we have the resources to do a HQ encoding?
 			///if (src->hq_encoder_c1 && (!hasChan2 || src->hq_encoder_c2)) {
@@ -502,7 +514,7 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) {
 								// TODO: Stagger the reset between nodes... random phasing
 								if (insert_iframes_ && fs.timestamp % (10*ftl::timer::getInterval()) == 0) enc->reset();
 
-								auto chan = fs.sources[j]->getChannel();
+								auto chan = second_channel_;
 
 								try {
 									enc->encode(fs.frames[j].get<cv::cuda::GpuMat>(chan), src->hq_bitrate, [this,src,hasChan2,chan,&cv,&chan2done](const ftl::codecs::Packet &blk){
@@ -523,7 +535,7 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) {
 					} else {
 						// Already have an encoding so send this
 						const auto &packets = fs.frames[j].getPackets(chan);
-						LOG(INFO) << "Send existing chan2 encoding: " << packets.size();
+						//LOG(INFO) << "Send existing chan2 encoding: " << packets.size();
 						for (const auto &i : packets) {
 							_transmitPacket(src, i, chan, hasChan2, Quality::High);	
 						}
@@ -554,7 +566,7 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) {
 					const auto &packets = fs.frames[j].getPackets(colChan);
 					// FIXME: Adjust block number and total to match number of packets
 					// Also requires the receiver to decode in block number order.
-					LOG(INFO) << "Send existing encoding: " << packets.size();
+					//LOG(INFO) << "Send existing encoding: " << packets.size();
 					for (const auto &i : packets) {
 						_transmitPacket(src, i, Channel::Colour, hasChan2, Quality::High);	
 					}
@@ -581,7 +593,7 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) {
 				// Important to send channel 2 first if needed...
 				// Receiver only waits for channel 1 by default
 				if (hasChan2) {
-					auto chan = fs.sources[j]->getChannel();
+					auto chan = second_channel_;
 
 					enc2->encode(fs.frames[j].get<cv::cuda::GpuMat>(chan), src->lq_bitrate, [this,src,hasChan2,chan](const ftl::codecs::Packet &blk){
 						_transmitPacket(src, blk, chan, hasChan2, Quality::Low);
@@ -613,15 +625,29 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) {
 }
 
 void Streamer::_transmitPacket(StreamSource *src, const ftl::codecs::Packet &pkt, Channel chan, bool hasChan2, Quality q) {
-	ftl::codecs::StreamPacket spkt = {
-		frame_no_,
-		src->id,
-		(hasChan2) ? 2 : 1,
-		chan
-		//static_cast<uint8_t>((chan & 0x1) | ((hasChan2) ? 0x2 : 0x0))
-	};
-
-	_transmitPacket(src, spkt, pkt, q);
+	const int version = 4;  // FIXME: Needs to be selected based on client version? Or removed when refactor complete.
+
+	if (version <= 3) {
+		ftl::codecs::StreamPacket spkt = {
+			3, // Version
+			frame_no_,
+			src->id,
+			(hasChan2) ? 2 : 1,
+			chan
+			//static_cast<uint8_t>((chan & 0x1) | ((hasChan2) ? 0x2 : 0x0))
+		};
+		_transmitPacket(src, spkt, pkt, q);
+	} else {
+		ftl::codecs::StreamPacket spkt = {
+			4, // Version
+			frame_no_,
+			0,
+			src->id,
+			chan
+			//static_cast<uint8_t>((chan & 0x1) | ((hasChan2) ? 0x2 : 0x0))
+		};
+		_transmitPacket(src, spkt, pkt, q);
+	}
 }
 
 void Streamer::_transmitPacket(StreamSource *src, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt, Quality q) {
@@ -650,7 +676,7 @@ void Streamer::_transmitPacket(StreamSource *src, const ftl::codecs::StreamPacke
 				(*c).txcount = (*c).txmax;
 			} else {
 				// Count frame as completed only if last block and channel is 0
-				if (pkt.block_number == pkt.block_total - 1 && spkt.channel == Channel::Colour) ++(*c).txcount;
+				if (spkt.channel == Channel::Colour) ++(*c).txcount; // pkt.block_number == pkt.block_total - 1 && 
 			}
 		} catch(...) {
 			(*c).txcount = (*c).txmax;
diff --git a/components/rgbd-sources/test/CMakeLists.txt b/components/rgbd-sources/test/CMakeLists.txt
index 9611e8081d942a6051e0ea254b3308d406161cd7..e16a37c5b7606f49bc3f06d7424ad12f726daee1 100644
--- a/components/rgbd-sources/test/CMakeLists.txt
+++ b/components/rgbd-sources/test/CMakeLists.txt
@@ -1,6 +1,7 @@
 ### Source Unit ################################################################
 add_executable(source_unit
 	./tests.cpp
+	../src/frame.cpp
 	./source_unit.cpp
 )
 target_include_directories(source_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
diff --git a/components/rgbd-sources/test/frame_unit.cpp b/components/rgbd-sources/test/frame_unit.cpp
index 6d858a62ab7765f1efc6f98ab09c5109f8101603..656971ae47b0dc8f6abc489854e05c7598589295 100644
--- a/components/rgbd-sources/test/frame_unit.cpp
+++ b/components/rgbd-sources/test/frame_unit.cpp
@@ -2,6 +2,7 @@
 #include <ftl/rgbd/frame.hpp>
 
 using ftl::rgbd::Frame;
+using ftl::rgbd::FrameState;
 using ftl::codecs::Channel;
 using ftl::codecs::Channels;
 using ftl::rgbd::Format;
@@ -275,9 +276,104 @@ TEST_CASE("Frame::swapTo()", "") {
 		Frame f2;
 
 		f1.create<cv::Mat>(Channel::Colour, Format<uchar3>(100,100));
-		f1.swapTo(Channels::All(), f2);
+		f1.swapTo(Channels<0>::All(), f2);
 
 		REQUIRE( f2.hasChannel(Channel::Colour) );
 		REQUIRE( (f2.get<cv::Mat>(Channel::Colour).cols == 100) );
 	}
 }
+
+TEST_CASE("Frame::setOrigin()", "") {
+	SECTION("With changed pose") {
+		Frame f;
+		FrameState s;
+
+		REQUIRE( !f.hasChanged(Channel::Pose) );
+
+		s.setPose(Eigen::Matrix4d());
+		f.setOrigin(&s);
+
+		REQUIRE( f.hasChanged(Channel::Pose) );
+	}
+
+	SECTION("With stale pose") {
+		Frame f;
+		FrameState s;
+
+		REQUIRE( !f.hasChanged(Channel::Pose) );
+
+		s.setPose(Eigen::Matrix4d());
+		f.setOrigin(&s);
+
+		f.reset();
+		f.setOrigin(&s);
+
+		REQUIRE( !f.hasChanged(Channel::Pose) );
+	}
+
+	SECTION("With updated pose") {
+		Frame f;
+		FrameState s;
+
+		REQUIRE( !f.hasChanged(Channel::Pose) );
+
+		s.setPose(Eigen::Matrix4d());
+		f.setOrigin(&s);
+
+		f.reset();
+		f.setOrigin(&s);
+
+		REQUIRE( !f.hasChanged(Channel::Pose) );
+
+		s.setPose(Eigen::Matrix4d());
+
+		REQUIRE( !f.hasChanged(Channel::Pose) );
+		REQUIRE( s.hasChanged(Channel::Pose) );
+	}
+
+	SECTION("Fail on multi set") {
+		Frame f;
+		FrameState s;
+
+		s.setPose(Eigen::Matrix4d());
+		f.setOrigin(&s);
+
+		bool failed = false;
+		try {
+			f.setOrigin(&s);
+		} catch (...) {
+			failed = true;
+		}
+
+		REQUIRE( failed );
+	}
+
+	SECTION("Reset and multi set") {
+		Frame f;
+		FrameState s;
+
+		s.setPose(Eigen::Matrix4d());
+		f.setOrigin(&s);
+
+		f.reset();
+		f.setOrigin(&s);
+	}
+}
+
+TEST_CASE("Frame::get() Pose", "") {
+	SECTION("Get valid pose") {
+		Frame f;
+		FrameState s;
+
+		Eigen::Matrix4d pose1;
+		s.setPose(pose1);
+		f.setOrigin(&s);
+
+		REQUIRE( f.hasChannel(Channel::Pose) );
+		REQUIRE( f.hasChanged(Channel::Pose) );
+
+		auto pose2 = f.getPose();
+
+		REQUIRE( pose1 == pose2 );
+	}
+}
diff --git a/components/rgbd-sources/test/source_unit.cpp b/components/rgbd-sources/test/source_unit.cpp
index 1b69631aa0ec0312cf1cba06533967f585babf56..d672632129b98d2a752c6a495b17b104d61a9a2f 100644
--- a/components/rgbd-sources/test/source_unit.cpp
+++ b/components/rgbd-sources/test/source_unit.cpp
@@ -146,45 +146,6 @@ TEST_CASE("ftl::create<Source>(cfg)", "[rgbd]") {
 	json_t global = json_t{{"$id","ftl://test"}};
 	ftl::config::configure(global);
 
-	SECTION("with valid image file uri") {
-		json_t cfg = json_t{
-			{"$id","ftl://test/1"},
-			{"uri","file://" FTL_SOURCE_DIRECTORY "/components/rgbd-sources/test/data/image.png"}
-		};
-
-		Source *src = ftl::create<Source>(cfg);
-
-		REQUIRE( src );
-		REQUIRE( src->isReady() );
-		REQUIRE( last_type == "image"); 
-	}
-
-	SECTION("with valid video file uri") {
-		json_t cfg = json_t{
-			{"$id","ftl://test/2"},
-			{"uri","file://" FTL_SOURCE_DIRECTORY "/components/rgbd-sources/test/data/video.mp4"}
-		};
-
-		Source *src = ftl::create<Source>(cfg);
-
-		REQUIRE( src );
-		REQUIRE( src->isReady() );
-		REQUIRE( last_type == "video");
-	}
-
-	SECTION("with valid net uri") {
-		json_t cfg = json_t{
-			{"$id","ftl://test/2"},
-			{"uri","ftl://utu.fi/dummy"}
-		};
-
-		Source *src = ftl::create<Source>(cfg);
-
-		REQUIRE( src );
-		REQUIRE( src->isReady() );
-		REQUIRE( last_type == "net");
-	}
-
 	SECTION("with an invalid uri") {
 		json_t cfg = json_t{
 			{"$id","ftl://test/2"},
@@ -197,50 +158,6 @@ TEST_CASE("ftl::create<Source>(cfg)", "[rgbd]") {
 		REQUIRE( !src->isReady() );
 	}
 
-	SECTION("with an invalid file uri") {
-		json_t cfg = json_t{
-			{"$id","ftl://test/2"},
-			{"uri","file:///not/a/file"}
-		};
-
-		Source *src = ftl::create<Source>(cfg);
-
-		REQUIRE( src );
-		REQUIRE( !src->isReady() );
-	}
-
-	SECTION("with a missing file") {
-		json_t cfg = json_t{
-			{"$id","ftl://test/2"},
-			{"uri","file:///data/image2.png"}
-		};
-
-		Source *src = ftl::create<Source>(cfg);
-
-		REQUIRE( src );
-		REQUIRE( !src->isReady() );
-	}
+	
 }
 
-TEST_CASE("Source::set(uri)", "[rgbd]") {
-	json_t global = json_t{{"$id","ftl://test"}};
-	ftl::config::configure(global);
-
-	SECTION("change to different valid URI type") {
-		json_t cfg = json_t{
-			{"$id","ftl://test/1"},
-			{"uri","file://" FTL_SOURCE_DIRECTORY "/components/rgbd-sources/test/data/image.png"}
-		};
-
-		Source *src = ftl::create<Source>(cfg);
-
-		REQUIRE( src );
-		REQUIRE( src->isReady() );
-		REQUIRE( last_type == "image" );
-
-		src->set("uri", "file://" FTL_SOURCE_DIRECTORY "/components/rgbd-sources/test/data/video.mp4");
-
-		REQUIRE( src->isReady() );
-		REQUIRE( last_type == "video" ); 
-	}
-}
diff --git a/components/streams/CMakeLists.txt b/components/streams/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..063f1d7678785408b31cc45aa75b0d311deabf5a
--- /dev/null
+++ b/components/streams/CMakeLists.txt
@@ -0,0 +1,24 @@
+set(STREAMSRC
+	src/stream.cpp
+	src/filestream.cpp
+	src/receiver.cpp
+	src/sender.cpp
+	src/netstream.cpp
+	src/injectors.cpp
+	src/parsers.cpp
+)
+
+add_library(ftlstreams ${STREAMSRC})
+
+# target_compile_options(ftlrgbd PUBLIC $<$<COMPILE_LANGUAGE:CXX>:-fPIC>)
+# target_compile_options(ftlrgbd PUBLIC "-DMAKE_SHARED")
+
+target_include_directories(ftlstreams PUBLIC
+	$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+	$<INSTALL_INTERFACE:include>
+	PRIVATE src)
+
+#target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include)
+target_link_libraries(ftlstreams ftlrgbd ftlcommon ${OpenCV_LIBS} Eigen3::Eigen ftlnet ftlcodecs)
+
+add_subdirectory(test)
\ No newline at end of file
diff --git a/components/streams/include/ftl/streams/filestream.hpp b/components/streams/include/ftl/streams/filestream.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7baffd75aab8f842d80e658905046604455bc3a4
--- /dev/null
+++ b/components/streams/include/ftl/streams/filestream.hpp
@@ -0,0 +1,75 @@
+#ifndef _FTL_STREAM_FILESTREAM_HPP_
+#define _FTL_STREAM_FILESTREAM_HPP_
+
+#include <ftl/streams/stream.hpp>
+
+namespace ftl {
+namespace stream {
+
+/**
+ * Provide a packet stream to/from a file. If the file already exists it is
+ * opened readonly, if not it is created write only. A mode to support both
+ * reading and writing (to re code it) could be supported by using a temp file
+ * for writing and swapping files when finished. It must be possible to control
+ * streaming rate from the file.
+ */
+class File : public Stream {
+	public:
+	explicit File(nlohmann::json &config);
+	File(nlohmann::json &config, std::ifstream *);
+	File(nlohmann::json &config, std::ofstream *);
+	~File();
+
+	bool onPacket(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &) override;
+
+	bool post(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &) override;
+
+	bool begin() override { return begin(true); }
+	bool begin(bool dorun);
+	bool end() override;
+	bool active() override;
+
+	/**
+	 * Automatically tick through the frames using a timer. Threads are used.
+	 */
+	bool run();
+
+	/**
+	 * Manually tick through the frames one per call.
+	 */
+	bool tick();
+
+	enum class Mode {
+		Read,
+		Write,
+		ReadWrite
+	};
+
+	inline void setMode(Mode m) { mode_ = m; }
+	inline void setStart(int64_t ts) { timestamp_ = ts; }
+
+	private:
+	std::ofstream *ostream_;
+	std::ifstream *istream_;
+
+	Mode mode_;
+	msgpack::sbuffer buffer_out_;
+	msgpack::unpacker buffer_in_;
+	std::list<std::tuple<ftl::codecs::StreamPacket,ftl::codecs::Packet>> data_;
+	int64_t timestart_;
+	int64_t timestamp_;
+	int64_t interval_;
+	bool active_;
+	int version_;
+	ftl::timer::TimerHandle timer_;
+
+	StreamCallback cb_;
+	MUTEX mutex_;
+	MUTEX data_mutex_;
+	std::atomic<int> jobs_;
+};
+
+}
+}
+
+#endif  // _FTL_STREAM_FILESTREAM_HPP_
diff --git a/components/streams/include/ftl/streams/netstream.hpp b/components/streams/include/ftl/streams/netstream.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..85edde6cfa7530bf93e70e3c15a0583e8c8da590
--- /dev/null
+++ b/components/streams/include/ftl/streams/netstream.hpp
@@ -0,0 +1,97 @@
+#ifndef _FTL_STREAM_NETSTREAM_HPP_
+#define _FTL_STREAM_NETSTREAM_HPP_
+
+#include <ftl/config.h>
+#include <ftl/net/universe.hpp>
+#include <ftl/threads.hpp>
+#include <ftl/codecs/packet.hpp>
+#include <ftl/streams/stream.hpp>
+#include <string>
+
+namespace ftl {
+namespace stream {
+
+namespace detail {
+struct StreamClient {
+	ftl::UUID peerid;
+	std::atomic<int> txcount;	// Frames sent since last request
+	int txmax;					// Frames to send in request
+	uint8_t quality;
+};
+}
+
+/**
+ * The maximum number of frames a client can request in a single request.
+ */
+static const int kMaxFrames = 100;
+
+/**
+ * Allows network streaming of a number of RGB-Depth sources. Remote machines
+ * can discover available streams from an instance of Streamer. It also allows
+ * for adaptive bitrate streams where bandwidth can be monitored and different
+ * data rates can be requested, it is up to the remote machine to decide on
+ * desired bitrate.
+ * 
+ * The capture and compression steps operate in different threads and each
+ * source and bitrate also operate on different threads. For a specific source
+ * and specific bitrate there is a single thread that sends data to all
+ * requesting clients.
+ * 
+ * Use ftl::create<Streamer>(parent, name) to construct, don't construct
+ * directly.
+ * 
+ * Note that the streamer attempts to maintain a fixed frame rate by
+ * monitoring job processing times and sleeping if there is spare time.
+ */
+class Net : public Stream {
+	public:
+	Net(nlohmann::json &config, ftl::net::Universe *net);
+	~Net();
+
+	bool onPacket(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &) override;
+
+	bool post(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &) override;
+
+	bool begin() override;
+	bool end() override;
+	bool active() override;
+
+	inline const ftl::UUID &getPeer() const { return peer_; }
+
+	private:
+	SHARED_MUTEX mutex_;
+	bool active_;
+	ftl::net::Universe *net_;
+	bool late_;
+	int64_t clock_adjust_;
+	ftl::UUID time_peer_;
+	ftl::UUID peer_;
+	int64_t last_frame_;
+	int64_t frame_no_;
+	std::string uri_;
+	bool host_;
+	int tally_;
+
+	float req_bitrate_;
+	float sample_count_;
+	int64_t last_msg_;
+	MUTEX msg_mtx_;
+
+	std::list<detail::StreamClient> clients_;
+
+	StreamCallback cb_;
+
+	bool _processRequest(ftl::net::Peer &p, const ftl::codecs::Packet &pkt);
+	void _checkDataRate(size_t tx_size, int64_t tx_latency, int64_t ts);
+	bool _sendRequest(ftl::codecs::Channel c, uint8_t frameset, uint8_t frames, uint8_t count, uint8_t bitrate);
+	void _cleanUp();
+	
+	//void _addClient(int N, int rate, const ftl::UUID &peer, const std::string &dest);
+	//void _transmitPacket(const ftl::codecs::Packet &pkt, ftl::codecs::Channel chan, int count);
+	//void _transmitPacket(const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt);
+};
+
+}
+}
+
+#endif  // _FTL_STREAM_NETSTREAM_HPP_
diff --git a/components/streams/include/ftl/streams/receiver.hpp b/components/streams/include/ftl/streams/receiver.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..e75c3b5987282caa445bcc4c0d612c4e28855e36
--- /dev/null
+++ b/components/streams/include/ftl/streams/receiver.hpp
@@ -0,0 +1,71 @@
+#ifndef _FTL_DATA_TRANSCODER_HPP_
+#define _FTL_DATA_TRANSCODER_HPP_
+
+#include <functional>
+#include <ftl/rgbd/frameset.hpp>
+#include <ftl/streams/stream.hpp>
+#include <ftl/codecs/decoder.hpp>
+
+namespace ftl {
+namespace stream {
+
+/**
+ * Convert packet streams into framesets.
+ */
+class Receiver : public ftl::Configurable, public ftl::rgbd::Generator {
+	public:
+	explicit Receiver(nlohmann::json &config);
+	~Receiver();
+
+	void setStream(ftl::stream::Stream*);
+
+	/**
+	 * Encode and transmit an entire frame set. Frames may already contain
+	 * an encoded form, in which case that is used instead.
+	 */
+	//void post(const ftl::rgbd::FrameSet &fs);
+
+	// void write(const ftl::audio::FrameSet &f);
+
+	size_t size() override;
+
+	ftl::rgbd::FrameState &state(int ix) override;
+
+	/**
+	 * Register a callback for received framesets. Sources are automatically
+	 * created to match the data received.
+	 */
+	void onFrameSet(const ftl::rgbd::VideoCallback &cb) override;
+
+	// void onFrameSet(const AudioCallback &cb);
+
+	private:
+	ftl::stream::Stream *stream_;
+	ftl::rgbd::VideoCallback fs_callback_;
+	ftl::rgbd::Builder builder_;
+	ftl::codecs::Channel second_channel_;
+	int64_t timestamp_;
+	SHARED_MUTEX mutex_;
+
+	struct InternalStates {
+		InternalStates();
+
+		int64_t timestamp;
+		ftl::rgbd::FrameState state;
+		ftl::rgbd::Frame frame;
+		ftl::codecs::Decoder* decoders[32];
+		MUTEX mutex;
+		ftl::codecs::Channels<0> completed;
+	};
+
+	std::vector<InternalStates*> frames_;
+
+	void _processConfig(InternalStates &frame, const ftl::codecs::Packet &pkt);
+	void _createDecoder(InternalStates &frame, int chan, const ftl::codecs::Packet &pkt);
+	InternalStates &_getFrame(const ftl::codecs::StreamPacket &spkt);
+};
+
+}
+}
+
+#endif  // _FTL_DATA_TRANSCODER_HPP_
diff --git a/components/streams/include/ftl/streams/sender.hpp b/components/streams/include/ftl/streams/sender.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2cfc57bb31d71cff17e322c36ec46960796ef13b
--- /dev/null
+++ b/components/streams/include/ftl/streams/sender.hpp
@@ -0,0 +1,47 @@
+#ifndef _FTL_STREAM_SENDER_HPP_
+#define _FTL_STREAM_SENDER_HPP_
+
+#include <functional>
+#include <ftl/rgbd/frameset.hpp>
+#include <ftl/streams/stream.hpp>
+#include <ftl/codecs/encoder.hpp>
+
+#include <unordered_map>
+
+namespace ftl {
+namespace stream {
+
+/**
+ * Convert framesets into packet streams.
+ */
+class Sender : public ftl::Configurable {
+	public:
+	explicit Sender(nlohmann::json &config);
+	~Sender();
+
+	void setStream(ftl::stream::Stream*);
+
+	/**
+	 * Encode and transmit an entire frame set. Frames may already contain
+	 * an encoded form, in which case that is used instead.
+	 */
+	void post(const ftl::rgbd::FrameSet &fs);
+
+	//void onStateChange(const std::function<void(ftl::codecs::Channel, int, int)>&);
+
+	private:
+	ftl::stream::Stream *stream_;
+	int64_t timestamp_;
+	SHARED_MUTEX mutex_;
+	std::atomic_flag do_inject_;
+	//std::function<void(ftl::codecs::Channel, int, int)> state_cb_;
+
+	std::unordered_map<int, ftl::codecs::Encoder*> encoders_;
+
+	ftl::codecs::Encoder *_getEncoder(int fsid, int fid, ftl::codecs::Channel c);
+};
+
+}
+}
+
+#endif  // _FTL_STREAM_SENDER_HPP_
diff --git a/components/streams/include/ftl/streams/stream.hpp b/components/streams/include/ftl/streams/stream.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..08064b6c9a6c69813930775ddac85d13f9ac3d9e
--- /dev/null
+++ b/components/streams/include/ftl/streams/stream.hpp
@@ -0,0 +1,190 @@
+#ifndef _FTL_STREAM_STREAM_HPP_
+#define _FTL_STREAM_STREAM_HPP_
+
+#include <loguru.hpp>
+#include <ftl/configuration.hpp>
+#include <ftl/configurable.hpp>
+#include <ftl/rgbd/source.hpp>
+#include <ftl/rgbd/group.hpp>
+#include <ftl/net/universe.hpp>
+#include <ftl/codecs/encoder.hpp>
+#include <ftl/threads.hpp>
+#include <string>
+#include <vector>
+#include <map>
+#include <atomic>
+
+namespace ftl {
+namespace stream {
+
+typedef std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> StreamCallback;
+
+/**
+ * Base stream class to be implemented. Provides encode and decode functionality
+ * around a generic packet read and write mechanism. Some specialisations will
+ * provide and automatically handle control signals.
+ * 
+ * Streams are bidirectional, frames can be both received and written.
+ */
+class Stream : public ftl::Configurable {
+	public:
+	explicit Stream(nlohmann::json &config) : ftl::Configurable(config) {};
+	virtual ~Stream() {};
+
+	/**
+	 * Obtain all packets for next frame. The provided callback function is
+	 * called once for every packet. This function might continue to call the
+	 * callback even after the read function returns, for example with a
+	 * NetStream.
+	 */
+	virtual bool onPacket(const StreamCallback &)=0;
+
+	virtual bool post(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)=0;
+
+	/**
+	 * Start the stream. Calls to the onPacket callback will only occur after
+	 * a call to this function (and before a call to end()).
+	 */
+	virtual bool begin()=0;
+
+	virtual bool end()=0;
+
+	/**
+	 * Is the stream active? Generally true if begin() has been called, false
+	 * initially and after end(). However, it may go false for other reasons.
+	 * If false, no calls to onPacket will occur and posts will be ignored.
+	 */
+	virtual bool active()=0;
+
+	/**
+	 * Query available video channels for a frameset.
+	 */
+	const ftl::codecs::Channels<0> &available(int fs) const;
+
+	/**
+	 * Query selected channels for a frameset. Channels not selected may not
+	 * be transmitted, received or decoded.
+	 */
+	const ftl::codecs::Channels<0> &selected(int fs) const;
+
+	/**
+	 * Change the video channel selection for a frameset.
+	 */
+	void select(int fs, const ftl::codecs::Channels<0> &);
+
+	/**
+	 * Number of framesets in stream.
+	 */
+	inline size_t size() const { return state_.size(); }
+
+	protected:
+
+	/**
+	 * Allow modification of available channels. Calling this with an invalid
+	 * fs number will create that frameset and increase the size.
+	 */
+	ftl::codecs::Channels<0> &available(int fs);
+
+	private:
+	struct FSState {
+		ftl::codecs::Channels<0> selected;
+		ftl::codecs::Channels<0> available;
+	};
+
+	std::vector<FSState> state_;
+	mutable SHARED_MUTEX mtx_;
+};
+
+/**
+ * Combine multiple streams into a single stream. StreamPackets are modified
+ * by mapping the stream identifiers consistently to new values. Both reading
+ * and writing are supported but a write must be preceeded by a read for the
+ * stream mapping to be registered.
+ */
+class Muxer : public Stream {
+	public:
+	explicit Muxer(nlohmann::json &config);
+	virtual ~Muxer();
+
+	void add(Stream *);
+
+	bool onPacket(const StreamCallback &) override;
+
+	bool post(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &) override;
+
+	bool begin() override;
+	bool end() override;
+	bool active() override;
+
+	private:
+	struct StreamEntry {
+		Stream *stream;
+		std::vector<int> maps;
+	};
+
+	std::vector<StreamEntry> streams_;
+	std::vector<std::pair<int,int>> revmap_;
+	int nid_;
+	StreamCallback cb_;
+	SHARED_MUTEX mutex_;
+
+	void _notify(const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt);
+	int _lookup(int sid, int ssid);
+};
+
+/**
+ * Forward all data to all child streams. Unlike the muxer which remaps the
+ * stream identifiers in the stream packet, this does not alter the stream
+ * packets.
+ */
+class Broadcast : public Stream {
+	public:
+	explicit Broadcast(nlohmann::json &config);
+	virtual ~Broadcast();
+
+	void add(Stream *);
+
+	bool onPacket(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &) override;
+
+	bool post(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &) override;
+
+	bool begin() override;
+	bool end() override;
+	bool active() override;
+
+	private:
+	std::list<Stream*> streams_;
+	StreamCallback cb_;
+	SHARED_MUTEX mutex_;
+};
+
+/**
+ * Allow packet interception by a callback between two other streams.
+ */
+class Intercept : public Stream {
+	public:
+	explicit Intercept(nlohmann::json &config);
+	virtual ~Intercept();
+
+	void setStream(Stream *);
+
+	bool onPacket(const StreamCallback &) override;
+	bool onIntercept(const StreamCallback &);
+
+	bool post(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &) override;
+
+	bool begin() override;
+	bool end() override;
+	bool active() override;
+
+	private:
+	Stream *stream_;
+	StreamCallback cb_;
+	StreamCallback intercept_;
+	SHARED_MUTEX mutex_;
+};
+
+}
+}
+
+#endif  // _FTL_STREAM_STREAM_HPP_
diff --git a/components/streams/src/filestream.cpp b/components/streams/src/filestream.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..232a929f6bf574baaa09e993e8a0be22ad7c9421
--- /dev/null
+++ b/components/streams/src/filestream.cpp
@@ -0,0 +1,277 @@
+#include <fstream>
+#include <ftl/streams/filestream.hpp>
+
+using ftl::stream::File;
+using ftl::codecs::StreamPacket;
+using ftl::codecs::Packet;
+using std::get;
+
+File::File(nlohmann::json &config) : Stream(config), ostream_(nullptr), istream_(nullptr), active_(false) {
+	mode_ = Mode::Read;
+	jobs_ = 0;
+}
+
+File::File(nlohmann::json &config, std::ifstream *is) : Stream(config), ostream_(nullptr), istream_(is), active_(false) {
+	mode_ = Mode::Read;
+	jobs_ = 0;
+}
+
+File::File(nlohmann::json &config, std::ofstream *os) : Stream(config), ostream_(os), istream_(nullptr), active_(false) {
+	mode_ = Mode::Write;
+	jobs_ = 0;
+}
+
+File::~File() {
+	end();
+}
+
+bool File::onPacket(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &f) {
+	cb_ = f;
+	return true;
+}
+
+bool File::post(const ftl::codecs::StreamPacket &s, const ftl::codecs::Packet &p) {
+	if (!active_) return false;
+	if (mode_ != Mode::Write) {
+		LOG(WARNING) << "Cannot write to read-only ftl file";
+		return false;
+	}
+
+	// Don't write dummy packets to files.
+	if (p.data.size() == 0) return true;
+
+	available(s.streamID) += s.channel;
+
+	ftl::codecs::StreamPacket s2 = s;
+	// Adjust timestamp relative to start of file.
+	s2.timestamp -= timestart_;
+
+	auto data = std::make_tuple(s2,p);
+	msgpack::sbuffer buffer;
+	msgpack::pack(buffer, data);
+
+	UNIQUE_LOCK(mutex_, lk);
+	ostream_->write(buffer.data(), buffer.size());
+	return ostream_->good();
+}
+
+bool File::tick() {
+	if (!active_) return false;
+	if (mode_ != Mode::Read) {
+		LOG(ERROR) << "Cannot read from a write only file";
+		return false;
+	}
+
+	//UNIQUE_LOCK(mtx_, lk);
+	std::unique_lock<std::mutex> lk(mutex_, std::defer_lock);
+	if (!lk.try_lock()) return true;
+
+	if (jobs_ > 0) {
+		//LOG(ERROR) << "STILL HAS JOBS";
+		return true;
+	}
+
+	// Check buffer first for frames already read
+	{
+		UNIQUE_LOCK(data_mutex_, dlk);
+		for (auto i = data_.begin(); i != data_.end(); ++i) {
+			if (std::get<0>(*i).timestamp <= timestamp_) {
+				++jobs_;
+				ftl::pool.push([this,i](int id) {
+					auto &spkt = std::get<0>(*i);
+					auto &pkt = std::get<1>(*i);
+
+					try {
+						if (cb_) cb_(spkt, pkt);
+					} catch (std::exception &e) {
+						LOG(ERROR) << "Exception in packet callback: " << e.what();
+					}
+					//LOG(INFO) << "ERASE: " << spkt.timestamp << ", " << spkt.frameNumber() << ", " << (int)spkt.channel;
+					UNIQUE_LOCK(data_mutex_, dlk);
+					data_.erase(i);
+					--jobs_;
+				});
+			}
+		}
+	}
+
+	bool partial = false;
+	int64_t extended_ts = timestamp_ + 200;  // Buffer 200ms ahead
+
+	while (active_ && istream_->good() || buffer_in_.nonparsed_size() > 0) {
+		if (buffer_in_.nonparsed_size() == 0 || (partial && buffer_in_.nonparsed_size() < 10000000)) {
+			buffer_in_.reserve_buffer(10000000);
+			istream_->read(buffer_in_.buffer(), buffer_in_.buffer_capacity());
+			//if (stream_->bad()) return false;
+
+			int bytes = istream_->gcount();
+			if (bytes == 0) break;
+			buffer_in_.buffer_consumed(bytes);
+			partial = false;
+		}
+
+		msgpack::object_handle msg;
+		if (!buffer_in_.next(msg)) {
+			partial = true;
+			continue;
+		}
+
+		msgpack::object obj = msg.get();
+
+		UNIQUE_LOCK(data_mutex_, dlk);
+		auto &data = data_.emplace_back();
+		dlk.unlock();
+
+		try {
+			obj.convert(data);
+		} catch (std::exception &e) {
+			LOG(INFO) << "Corrupt message: " << buffer_in_.nonparsed_size() << " - " << e.what();
+			active_ = false;
+			return false;
+		}
+
+		// Adjust timestamp
+		std::get<0>(data).timestamp = ((std::get<0>(data).timestamp) / interval_) * interval_ + timestart_;
+		//std::get<0>(data).timestamp = std::get<0>(data).timestamp + timestart_;
+
+		// Fix to clear flags for version 2.
+		if (version_ <= 2) {
+			std::get<1>(data).flags = 0;
+		}
+		if (version_ < 4) {
+			std::get<0>(data).frame_number = std::get<0>(data).streamID;
+			std::get<0>(data).streamID = 0;
+		}
+		std::get<0>(data).version = 4;
+
+		// Maintain availability of channels.
+		available(0) += std::get<0>(data).channel;
+
+		// This should only occur for first few frames, generally otherwise
+		// the data buffer is already several frames ahead so is processed
+		// above. Hence, no need to bother parallelising this bit.
+		if (std::get<0>(data).timestamp <= timestamp_) {
+			if (cb_) {
+				dlk.lock();
+				try {
+					cb_(std::get<0>(data),std::get<1>(data));
+				} catch (std::exception &e) {
+					LOG(ERROR) << "Exception in packet callback: " << e.what();
+				}
+				data_.pop_back();
+			}
+		} else if (std::get<0>(data).timestamp > extended_ts) {
+			break;
+		}
+	}
+
+	timestamp_ += interval_;
+
+	if (data_.size() == 0 && value("looping", true)) {
+		istream_->clear();
+        istream_->seekg(0);
+
+		ftl::codecs::Header h;
+		(*istream_).read((char*)&h, sizeof(h));
+		if (h.version >= 2) {
+			ftl::codecs::IndexHeader ih;
+			(*istream_).read((char*)&ih, sizeof(ih));
+		}
+
+		timestart_ = (ftl::timer::get_time() / ftl::timer::getInterval()) * ftl::timer::getInterval();
+		timestamp_ = timestart_;
+		return true;
+	}
+
+	return data_.size() > 0;
+}
+
+bool File::run() {
+	timer_ = ftl::timer::add(ftl::timer::kTimerMain, [this](int64_t ts) {
+		tick();
+		return active_;
+	});
+	return true;
+}
+
+bool File::begin(bool dorun) {
+	if (mode_ == Mode::Read) {
+		if (!istream_) istream_ = new std::ifstream;
+		istream_->open(*get<std::string>("filename"));
+
+		if (!istream_->good()) {
+			LOG(ERROR) << "Could not open file: " << *get<std::string>("filename");
+			return false;
+		}
+
+		ftl::codecs::Header h;
+		(*istream_).read((char*)&h, sizeof(h));
+		if (h.magic[0] != 'F' || h.magic[1] != 'T' || h.magic[2] != 'L' || h.magic[3] != 'F') return false;
+
+		if (h.version >= 2) {
+			ftl::codecs::IndexHeader ih;
+			(*istream_).read((char*)&ih, sizeof(ih));
+		}
+
+		version_ = h.version;
+		LOG(INFO) << "FTL format version " << version_;
+
+		// Capture current time to adjust timestamps
+		timestart_ = (ftl::timer::get_time() / ftl::timer::getInterval()) * ftl::timer::getInterval();
+		active_ = true;
+		interval_ = ftl::timer::getInterval();
+		timestamp_ = timestart_;
+
+		tick(); // Do some now!
+		if (dorun) run();
+	} else if (mode_ == Mode::Write) {
+		if (!ostream_) ostream_ = new std::ofstream;
+		ostream_->open(*get<std::string>("filename"));
+
+		if (!ostream_->good()) {
+			LOG(ERROR) << "Could not open file: " << *get<std::string>("filename");
+			return false;
+		}
+
+		ftl::codecs::Header h;
+		//h.version = 2;
+		(*ostream_).write((const char*)&h, sizeof(h));
+
+		ftl::codecs::IndexHeader ih;
+		ih.reserved[0] = -1;
+		(*ostream_).write((const char*)&ih, sizeof(ih));
+
+		// Capture current time to adjust timestamps
+		timestart_ = ftl::timer::get_time();
+		active_ = true;
+		interval_ = ftl::timer::getInterval();
+		timestamp_ = timestart_;
+	}
+
+	return true;
+}
+
+bool File::end() {
+	if (!active_) return false;
+	active_ = false;
+	timer_.cancel();
+
+	if (mode_ == Mode::Read) {
+		if (istream_) {
+			istream_->close();
+			delete istream_;
+			istream_ = nullptr;
+		}
+	} else if (mode_ == Mode::Write) {
+		if (ostream_) {
+			ostream_->close();
+			delete ostream_;
+			ostream_ = nullptr;
+		}
+	}
+	return true;
+}
+
+bool File::active() {
+	return active_;
+}
diff --git a/components/streams/src/injectors.cpp b/components/streams/src/injectors.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fdc27df8a898ec97ee5baa0a182faf7baca133ef
--- /dev/null
+++ b/components/streams/src/injectors.cpp
@@ -0,0 +1,94 @@
+#include "injectors.hpp"
+
+using ftl::codecs::Channel;
+
+class VectorBuffer2 {
+	public:
+	inline explicit VectorBuffer2(std::vector<unsigned char> &v) : vector_(v) {}
+
+	inline void write(const char *data, std::size_t size) {
+		vector_.insert(vector_.end(), (const unsigned char*)data, (const unsigned char*)data+size);
+	}
+
+	private:
+	std::vector<unsigned char> &vector_;
+};
+
+void ftl::stream::injectCalibration(ftl::stream::Stream *stream, const ftl::rgbd::FrameSet &fs, int ix, bool right) {
+	ftl::stream::injectCalibration(stream, fs.frames[ix], fs.timestamp, ix, right);
+}
+
+void ftl::stream::injectPose(ftl::stream::Stream *stream, const ftl::rgbd::FrameSet &fs, int ix) {
+	ftl::stream::injectPose(stream, fs.frames[ix], fs.timestamp, ix);
+}
+
+void ftl::stream::injectConfig(ftl::stream::Stream *stream, const ftl::rgbd::FrameSet &fs, int ix) {
+	ftl::codecs::StreamPacket spkt = {
+		4,
+		fs.timestamp,
+		0,
+		static_cast<uint8_t>(ix),
+		Channel::Configuration
+	};
+
+	ftl::codecs::Packet pkt;
+	pkt.codec = ftl::codecs::codec_t::MSGPACK;
+	pkt.definition = ftl::codecs::definition_t::Any;
+	pkt.bitrate = 0;
+	pkt.frame_count = 1;
+	pkt.flags = 0;
+
+	VectorBuffer2 buf(pkt.data);
+	msgpack::pack(buf, fs.frames[ix].getConfigString());
+
+	stream->post(spkt, pkt);
+}
+
+void ftl::stream::injectPose(ftl::stream::Stream *stream, const ftl::rgbd::Frame &f, int64_t ts, int ix) {
+    ftl::codecs::StreamPacket spkt = {
+		4,
+		ts,
+		0,
+		static_cast<uint8_t>(ix),
+		Channel::Pose
+	};
+
+	ftl::codecs::Packet pkt;
+	pkt.codec = ftl::codecs::codec_t::MSGPACK;
+	pkt.definition = ftl::codecs::definition_t::Any;
+	pkt.bitrate = 0;
+	pkt.frame_count = 1;
+	pkt.flags = 0;
+
+	auto &pose = f.getPose();
+	std::vector<double> data(pose.data(), pose.data() + 4*4);
+	VectorBuffer2 buf(pkt.data);
+	msgpack::pack(buf, data);
+
+	stream->post(spkt, pkt);
+}
+
+void ftl::stream::injectCalibration(ftl::stream::Stream *stream, const ftl::rgbd::Frame &f, int64_t ts, int ix, bool right) {
+    ftl::codecs::StreamPacket spkt = {
+		4,
+		ts,
+		0,
+		static_cast<uint8_t>(ix),
+		(right) ? Channel::Calibration2 : Channel::Calibration
+	};
+
+	auto data = (right) ?
+		std::make_tuple(f.getRightCamera(), Channel::Right, 0) :
+		std::make_tuple(f.getLeftCamera(), Channel::Left, 0);
+
+	ftl::codecs::Packet pkt;
+	pkt.codec = ftl::codecs::codec_t::MSGPACK;
+	pkt.definition = ftl::codecs::definition_t::Any;
+	pkt.bitrate = 0;
+	pkt.frame_count = 1;
+	pkt.flags = 0;
+
+	VectorBuffer2 buf(pkt.data);
+	msgpack::pack(buf, data);
+	stream->post(spkt, pkt);
+}
diff --git a/components/streams/src/injectors.hpp b/components/streams/src/injectors.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6fbb76031c7213545fb1cb9e9708764b936a00ac
--- /dev/null
+++ b/components/streams/src/injectors.hpp
@@ -0,0 +1,19 @@
+#ifndef _FTL_STREAM_INJECTORS_HPP_
+#define _FTL_STREAM_INJECTORS_HPP_
+
+#include <ftl/streams/stream.hpp>
+
+namespace ftl {
+namespace stream {
+
+void injectPose(ftl::stream::Stream *stream, const ftl::rgbd::FrameSet &fs, int ix);
+void injectCalibration(ftl::stream::Stream *stream, const ftl::rgbd::FrameSet &fs, int ix, bool right=false);
+void injectConfig(ftl::stream::Stream *stream, const ftl::rgbd::FrameSet &fs, int ix);
+
+void injectPose(ftl::stream::Stream *stream, const ftl::rgbd::Frame &fs, int64_t ts, int ix);
+void injectCalibration(ftl::stream::Stream *stream, const ftl::rgbd::Frame &fs, int64_t ts, int ix, bool right=false);
+
+}
+}
+
+#endif  // _FTL_STREAM_INJECTORS_HPP_
diff --git a/components/streams/src/netstream.cpp b/components/streams/src/netstream.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..964965edf6cabbcae39b5d999292b0a0a2ea726d
--- /dev/null
+++ b/components/streams/src/netstream.cpp
@@ -0,0 +1,325 @@
+#include <ftl/streams/netstream.hpp>
+
+using ftl::stream::Net;
+using ftl::codecs::StreamPacket;
+using ftl::codecs::Packet;
+using ftl::codecs::Channel;
+using ftl::codecs::codec_t;
+using ftl::codecs::definition_t;
+using ftl::codecs::kAllFrames;
+using ftl::codecs::kAllFramesets;
+using std::string;
+using std::vector;
+using std::optional;
+
+Net::Net(nlohmann::json &config, ftl::net::Universe *net) : Stream(config), net_(net), active_(false) {
+	// TODO: Install "find_stream" binding if not installed...
+	if (!net_->isBound("find_stream")) {
+		net_->bind("find_stream", [this](const std::string &uri) -> optional<ftl::UUID> {
+			LOG(INFO) << "REQUEST FIND STREAM: " << uri;
+			if (uri_ == uri) {
+				return net_->id();
+			} else {
+				return {};
+			}
+		});
+	}
+
+	if (!net_->isBound("list_streams")) {
+		net_->bind("list_streams", [this]() {
+			LOG(INFO) << "REQUEST LIST STREAMS";
+			vector<string> streams;
+			streams.push_back(uri_);
+			return streams;
+		});
+	}
+
+	last_frame_ = 0;
+	time_peer_ = ftl::UUID(0);
+}
+
+Net::~Net() {
+	end();
+}
+
+bool Net::onPacket(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &f) {
+	cb_ = f;
+	return true;
+}
+
+bool Net::post(const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+	if (!active_) return false;
+
+	// Lock to prevent clients being added / removed
+	{
+		SHARED_LOCK(mutex_,lk);
+		available(spkt.frameSetID()) += spkt.channel;
+
+		if (host_) {
+			auto c = clients_.begin();
+			while (c != clients_.end()) {
+				auto &client = *c;
+
+				// Quality filter the packets
+				if (client.quality >= 0 && pkt.bitrate != client.quality) {
+					++c;
+					LOG(INFO) << "INCORRECT QUALITY";
+					continue;
+				}
+
+				try {
+					// FIXME: This doesn't work for file sources with file relative timestamps...
+					short pre_transmit_latency = short(ftl::timer::get_time() - spkt.timestamp);
+					if (!net_->send(client.peerid,
+							uri_,
+							pre_transmit_latency,  // Time since timestamp for tx
+							spkt,
+							pkt)) {
+
+						// Send failed so mark as client stream completed
+						client.txcount = client.txmax;
+					} else {
+						// Count frame as completed only if last block and channel is 0
+						if (spkt.streamID == 0 && spkt.frame_number == 0 && spkt.channel == Channel::Colour) ++client.txcount;
+					}
+				} catch(...) {
+					client.txcount = client.txmax;
+				}
+				++c;
+			}
+		} else {
+			try {
+				short pre_transmit_latency = short(ftl::timer::get_time() - spkt.timestamp);
+				if (!net_->send(peer_,
+						uri_,
+						pre_transmit_latency,  // Time since timestamp for tx
+						spkt,
+						pkt)) {
+
+				} else {
+					// TODO: Some disconnect error
+				}
+			} catch(...) {
+				// TODO: Some disconnect error
+			}
+		}
+	}
+
+	_cleanUp();
+
+	return true;
+}
+
+bool Net::begin() {
+	if (!get<string>("uri")) return false;
+	active_ = true;
+
+	uri_ = *get<string>("uri");
+
+	if (net_->isBound(uri_)) {
+		LOG(ERROR) << "Stream already exists! - " << uri_;
+		active_ = false;
+		return false;
+	}
+
+	net_->bind(uri_, [this](ftl::net::Peer &p, short ttimeoff, const ftl::codecs::StreamPacket &spkt_raw, const ftl::codecs::Packet &pkt) {
+		int64_t now = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count();
+
+		if (!active_) return;
+
+		StreamPacket spkt = spkt_raw;
+		spkt.version = 4;
+
+		// Manage recuring requests
+		if (last_frame_ != spkt.timestamp) {
+			UNIQUE_LOCK(mutex_, lk);
+			if (last_frame_ != spkt.timestamp) {
+				last_frame_ = spkt.timestamp;
+				if (tally_ <= 5) {
+					if (size() > 0) {
+						auto sel = selected(0);
+						// FIXME: Send selection changes immediately.
+						for (auto c : sel) {
+							_sendRequest(c, kAllFramesets, kAllFrames, 30, 0);
+						}
+					}
+					tally_ = 30;
+				} else {
+					--tally_;
+				}
+			}
+		}
+
+		// If hosting and no data then it is a request for data
+		// Note: a non host can receive empty data, meaning data is available
+		// but that you did not request it
+		if (host_ && pkt.data.size() == 0) {
+			// FIXME: Allow unselecting ...?
+			if (spkt.frameSetID() == 255) {
+				for (int i=0; i<size(); ++i) {
+					select(i, selected(i) + spkt.channel);
+				}
+			} else {
+				select(spkt.frameSetID(), selected(spkt.frameSetID()) + spkt.channel);
+			}
+			_processRequest(p, pkt);
+		} else {
+			// FIXME: Allow availability to change...
+			available(spkt.frameSetID()) += spkt.channel;
+			//LOG(INFO) << "AVAILABLE: " << (int)spkt.channel;
+		}
+
+		if (cb_) {
+			cb_(spkt, pkt);
+			if (pkt.data.size() > 0) _checkDataRate(pkt.data.size(), now-(spkt.timestamp+ttimeoff), spkt.timestamp);
+		}
+	});
+
+	auto p = net_->findOne<ftl::UUID>("find_stream", uri_);
+	if (!p) {
+		LOG(INFO) << "Hosting stream: " << uri_;
+		// TODO: Register URI as available.
+		host_ = true;
+		return true;
+	}
+
+	host_ = false;
+	peer_ = *p;
+	tally_ = 30;
+	
+	// Initially send a colour request just to create the connection
+	_sendRequest(Channel::Colour, kAllFramesets, kAllFrames, 30, 0);
+
+	return true;
+}
+
+bool Net::_sendRequest(Channel c, uint8_t frameset, uint8_t frames, uint8_t count, uint8_t bitrate) {
+	if (!active_ || host_) return false;
+
+	//LOG(INFO) << "SENDING REQUEST FOR " << (int)c;
+
+	Packet pkt = {
+		codec_t::Any,			// TODO: Allow specific codec requests
+		definition_t::Any,		// TODO: Allow specific definition requests
+		count,
+		bitrate
+	};
+
+	StreamPacket spkt = {
+		4,
+		ftl::timer::get_time(),
+		frameset,
+		frames,
+		c
+	};
+
+	net_->send(peer_, uri_, (short)0, spkt, pkt);
+
+	return true;
+}
+
+void Net::_cleanUp() {
+	UNIQUE_LOCK(mutex_,lk);
+	for (auto i=clients_.begin(); i!=clients_.end(); ++i) {
+		auto &client = *i;
+		if (client.txcount >= client.txmax) {
+			if (client.peerid == time_peer_) {
+				time_peer_ = ftl::UUID(0);
+			}
+			LOG(INFO) << "Remove peer: " << client.peerid.to_string();
+			i = clients_.erase(i);
+		}
+	}
+}
+
+/* Packets for specific framesets, frames and channels are requested in
+ * batches (max 255 unique frames by timestamp). Requests are in the form
+ * of packets that match the request except the data component is empty.
+ */
+bool Net::_processRequest(ftl::net::Peer &p, const ftl::codecs::Packet &pkt) {
+	{
+		UNIQUE_LOCK(mutex_,lk);
+		bool found = false;
+
+		// Does the client already exist
+		for (auto &c : clients_) {
+			if (c.peerid == p.id()) {
+				// Yes, so reset internal request counters
+				c.txcount = 0;
+				c.txmax = pkt.frame_count;
+				found = true;
+			}
+		}
+
+		// No existing client, so add a new one.
+		if (!found) {
+			auto &client = clients_.emplace_back();
+			client.peerid = p.id();
+			client.quality = 0;  // TODO: Use quality given in packet
+			client.txcount = 0;
+			client.txmax = pkt.frame_count;
+		}
+
+		// First connected peer (or reconnecting peer) becomes a time server
+		if (time_peer_ == ftl::UUID(0)) {
+			time_peer_ = p.id();
+			DLOG(INFO) << "Adding time peer";
+		}
+	}
+
+	// Sync clocks!
+	if (p.id() == time_peer_) {
+		auto start = std::chrono::high_resolution_clock::now();
+		int64_t mastertime;
+
+		try {
+			mastertime = net_->call<int64_t>(time_peer_, "__ping__");
+		} catch (...) {
+			LOG(ERROR) << "Ping failed";
+			// Reset time peer and remove timer
+			time_peer_ = ftl::UUID(0);
+			return false;
+		}
+
+		auto elapsed = std::chrono::high_resolution_clock::now() - start;
+		int64_t latency = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
+		auto clock_adjust = mastertime - (ftl::timer::get_time() + (latency/2));
+
+		if (clock_adjust > 0) {
+			LOG(INFO) << "Clock adjustment: " << clock_adjust;
+			//LOG(INFO) << "Latency: " << (latency / 2);
+			//LOG(INFO) << "Local: " << std::chrono::time_point_cast<std::chrono::milliseconds>(start).time_since_epoch().count() << ", master: " << mastertime;
+			ftl::timer::setClockAdjustment(clock_adjust);
+		}
+	}
+
+	return false;
+}
+
+void Net::_checkDataRate(size_t tx_size, int64_t tx_latency, int64_t ts) {
+	float actual_mbps = (float(tx_size) * 8.0f * (1000.0f / float(tx_latency))) / 1048576.0f;
+    float min_mbps = (float(tx_size) * 8.0f * (1000.0f / float(ftl::timer::getInterval()))) / 1048576.0f;
+    if (actual_mbps > 0.0f && actual_mbps < min_mbps) LOG(WARNING) << "Bitrate = " << actual_mbps << "Mbps, min required = " << min_mbps << "Mbps";
+
+	UNIQUE_LOCK(msg_mtx_,lk);
+	req_bitrate_ += float(tx_size) * 8.0f;
+	sample_count_ += 1.0f;
+
+	if (ts - last_msg_ >= 1000) {
+		LOG(INFO) << "Required Bitrate = " << (req_bitrate_ / float(ts - last_msg_) * 1000.0f / 1048576.0f) << "Mbps";
+		last_msg_ = ts;
+		req_bitrate_ = 0.0f;
+		sample_count_ = 0.0f;
+	}
+}
+
+bool Net::end() {
+	if (!active_) return false;
+	active_ = false;
+	net_->unbind(uri_);
+	return true;
+}
+
+bool Net::active() {
+	return active_;
+}
diff --git a/components/streams/src/parsers.cpp b/components/streams/src/parsers.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ebca4576bfb14f4919aaea5ad4376e0bd5a667a6
--- /dev/null
+++ b/components/streams/src/parsers.cpp
@@ -0,0 +1,39 @@
+#include "parsers.hpp"
+
+#include <loguru.hpp>
+
+#include <ftl/codecs/channels.hpp>
+#include <tuple>
+
+ftl::rgbd::Camera ftl::stream::parseCalibration(const ftl::codecs::Packet &pkt) {
+	std::tuple<ftl::rgbd::Camera, ftl::codecs::Channel, unsigned int> params;
+	auto unpacked = msgpack::unpack((const char*)pkt.data.data(), pkt.data.size());
+	unpacked.get().convert(params);
+
+	LOG(INFO) << "Got Calibration: " << std::get<0>(params).width << "x" << std::get<0>(params).height;
+	return std::get<0>(params);
+}
+
+Eigen::Matrix4d ftl::stream::parsePose(const ftl::codecs::Packet &pkt) {
+    Eigen::Matrix4d p;
+
+	if (pkt.codec == ftl::codecs::codec_t::POSE) {
+		p = Eigen::Map<Eigen::Matrix4d>((double*)pkt.data.data());
+	} else if (pkt.codec == ftl::codecs::codec_t::MSGPACK) {
+		auto unpacked = msgpack::unpack((const char*)pkt.data.data(), pkt.data.size());
+		std::vector<double> posevec;
+		unpacked.get().convert(posevec);
+		p = Eigen::Matrix4d(posevec.data());
+	}
+
+    return p;
+}
+
+std::string ftl::stream::parseConfig(const ftl::codecs::Packet &pkt) {
+	std::string cfg;
+	auto unpacked = msgpack::unpack((const char*)pkt.data.data(), pkt.data.size());
+	unpacked.get().convert(cfg);
+
+	LOG(INFO) << "Config Received: " << cfg;
+	return cfg;
+}
diff --git a/components/streams/src/parsers.hpp b/components/streams/src/parsers.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..e463672d8819109bf58a5d1a7294e41024d19313
--- /dev/null
+++ b/components/streams/src/parsers.hpp
@@ -0,0 +1,18 @@
+#ifndef _FTL_STREAM_PARSERS_HPP_
+#define _FTL_STREAM_PARSERS_HPP_
+
+#include <ftl/rgbd/camera.hpp>
+#include <ftl/codecs/packet.hpp>
+#include <Eigen/Eigen>
+
+namespace ftl {
+namespace stream {
+
+Eigen::Matrix4d parsePose(const ftl::codecs::Packet &pkt);
+ftl::rgbd::Camera parseCalibration(const ftl::codecs::Packet &pkt);
+std::string parseConfig(const ftl::codecs::Packet &pkt);
+
+}
+}
+
+#endif  // _FTL_STREAM_PARSERS_HPP_
diff --git a/components/streams/src/receiver.cpp b/components/streams/src/receiver.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..36b454759e84be7d63fbb12596daab67fa4b5d8d
--- /dev/null
+++ b/components/streams/src/receiver.cpp
@@ -0,0 +1,190 @@
+#include <ftl/streams/receiver.hpp>
+
+#include "parsers.hpp"
+#include "injectors.hpp"
+
+using ftl::stream::Receiver;
+using ftl::stream::Stream;
+using ftl::codecs::StreamPacket;
+using ftl::codecs::Packet;
+using ftl::codecs::Channel;
+using std::string;
+using ftl::stream::parseCalibration;
+using ftl::stream::parsePose;
+using ftl::stream::parseConfig;
+using ftl::stream::injectCalibration;
+using ftl::stream::injectPose;
+
+Receiver::Receiver(nlohmann::json &config) : ftl::Configurable(config), stream_(nullptr) {
+	timestamp_ = 0;
+	second_channel_ = Channel::Depth;
+}
+
+Receiver::~Receiver() {
+
+}
+
+/*void Receiver::_processConfig(InternalStates &frame, const ftl::codecs::Packet &pkt) {
+	std::string cfg;
+	auto unpacked = msgpack::unpack((const char*)pkt.data.data(), pkt.data.size());
+	unpacked.get().convert(cfg);
+
+	LOG(INFO) << "Config Received: " << cfg;
+	// TODO: This needs to be put in safer / better location
+	//host_->set(std::get<0>(cfg), nlohmann::json::parse(std::get<1>(cfg)));
+}*/
+
+void Receiver::_createDecoder(InternalStates &frame, int chan, const ftl::codecs::Packet &pkt) {
+	//UNIQUE_LOCK(mutex_,lk);
+	auto *decoder = frame.decoders[chan];
+	if (decoder) {
+		if (!decoder->accepts(pkt)) {
+			//UNIQUE_LOCK(mutex_,lk);
+			ftl::codecs::free(frame.decoders[chan]);
+		} else {
+			return;
+		}
+	}
+
+	//UNIQUE_LOCK(mutex_,lk);
+	frame.decoders[chan] = ftl::codecs::allocateDecoder(pkt);
+}
+
+Receiver::InternalStates::InternalStates() {
+	for (int i=0; i<32; ++i) decoders[i] = nullptr;
+}
+
+Receiver::InternalStates &Receiver::_getFrame(const StreamPacket &spkt) {
+	UNIQUE_LOCK(mutex_, lk);
+	while (frames_.size() <= spkt.frameNumber()) {
+		//frames_.resize(spkt.frameNumber()+1);
+		frames_.push_back(new InternalStates);
+		frames_[frames_.size()-1]->state.set("name",std::string("Source ")+std::to_string(spkt.frameNumber()+1));
+	}
+	auto &f = *frames_[spkt.frameNumber()];
+	if (!f.frame.origin()) f.frame.setOrigin(&f.state);
+	return f;
+}
+
+void Receiver::setStream(ftl::stream::Stream *s) {
+	if (stream_) {
+		stream_->onPacket(nullptr);
+	}
+
+	stream_ = s;
+
+	s->onPacket([this](const StreamPacket &spkt, const Packet &pkt) {	
+		//const ftl::codecs::Channel chan = second_channel_;
+		const ftl::codecs::Channel rchan = spkt.channel;
+		const unsigned int channum = (unsigned int)spkt.channel;
+
+		//LOG(INFO) << "PACKET: " << spkt.timestamp << ", " << (int)spkt.channel << ", " << (int)pkt.codec;
+
+		// TODO: Allow for multiple framesets
+		if (spkt.frameSetID() > 0) return;
+
+		// Too many frames, so ignore.
+		if (spkt.frameNumber() >= value("max_frames",32)) return;
+
+		// Dummy no data packet.
+		if (pkt.data.size() == 0) return;
+
+		InternalStates &frame = _getFrame(spkt);
+
+		//if (spkt.timestamp > frame.timestamp && !frame.completed.empty()) {
+		//	LOG(WARNING) << "Next frame received";
+			//return;
+		//}
+
+		// Deal with the special channels...
+		switch (rchan) {
+		case Channel::Configuration		: frame.state.getConfig() = nlohmann::json::parse(parseConfig(pkt)); return;
+		case Channel::Calibration		: frame.state.getLeft() = parseCalibration(pkt); return;
+		case Channel::Calibration2		: frame.state.getRight() = parseCalibration(pkt); return;
+		case Channel::Pose				: frame.state.getPose() = parsePose(pkt); return;
+		default: break;
+		}
+
+		// Packets are for unwanted channel.
+		//if (rchan != Channel::Colour && rchan != chan) return;
+
+		if (frame.frame.hasChannel(spkt.channel)) {
+			// FIXME: Is this a corruption in recording or in playback?
+			// Seems to occur in same place in ftl file, one channel is missing
+			LOG(ERROR) << "Previous frame not complete: " << frame.timestamp;
+			//LOG(ERROR) << " --- " << (string)spkt;
+			UNIQUE_LOCK(frame.mutex, lk);
+			frame.frame.reset();
+			frame.completed.clear();
+		}
+		frame.timestamp = spkt.timestamp;
+
+		// Add channel to frame and allocate memory if required
+		const cv::Size size = cv::Size(ftl::codecs::getWidth(pkt.definition), ftl::codecs::getHeight(pkt.definition));
+		frame.frame.create<cv::cuda::GpuMat>(spkt.channel).create(size, (isFloatChannel(rchan) ? CV_32FC1 : CV_8UC4));
+
+		Packet tmppkt = pkt;
+		frame.frame.pushPacket(spkt.channel, tmppkt);
+
+		//LOG(INFO) << " CODEC = " << (int)pkt.codec << " " << (int)pkt.flags << " " << (int)spkt.channel;
+
+		// Do the actual decode
+		_createDecoder(frame, channum, pkt);
+		auto *decoder = frame.decoders[channum];
+		if (!decoder) {
+			LOG(ERROR) << "No frame decoder available";
+			return;
+		}
+
+		try {
+			//LOG(INFO) << "TYPE = " << frame.frame.get<cv::cuda::GpuMat>(spkt.channel).type();
+			decoder->decode(pkt, frame.frame.get<cv::cuda::GpuMat>(spkt.channel));
+		} catch (std::exception &e) {
+			LOG(ERROR) << "Decode failed for " << spkt.timestamp << ": " << e.what();
+		}
+
+		frame.completed += spkt.channel;
+		
+		// Complete if all requested frames are found
+		auto sel = stream_->selected(spkt.frameSetID());
+
+		if ((frame.completed & sel) == sel) {
+			UNIQUE_LOCK(frame.mutex, lk);
+			if ((frame.completed & sel) == sel) {
+				timestamp_ = frame.timestamp;
+
+				//LOG(INFO) << "BUILDER PUSH: " << timestamp_ << ", " << spkt.frameNumber();
+
+				if (frame.state.getLeft().width == 0) {
+					LOG(WARNING) << "Missing calibration, skipping frame";
+					//frame.frame.reset();
+					//frame.completed.clear();
+					//return;
+				}
+
+				// TODO: Have multiple builders for different framesets.
+				builder_.push(frame.timestamp, spkt.frameNumber(), frame.frame);
+
+				// Check for any state changes and send them back
+				if (frame.state.hasChanged(Channel::Pose)) injectPose(stream_, frame.frame, frame.timestamp, spkt.frameNumber());
+				if (frame.state.hasChanged(Channel::Calibration)) injectCalibration(stream_, frame.frame, frame.timestamp, spkt.frameNumber());
+				if (frame.state.hasChanged(Channel::Calibration2)) injectCalibration(stream_, frame.frame, frame.timestamp, spkt.frameNumber(), true);
+
+				frame.frame.reset();
+				frame.completed.clear();
+			}
+		}
+	});
+}
+
+size_t Receiver::size() {
+	return builder_.size();
+}
+
+ftl::rgbd::FrameState &Receiver::state(int ix) {
+	return builder_.state(ix);
+}
+
+void Receiver::onFrameSet(const ftl::rgbd::VideoCallback &cb) {
+	builder_.onFrameSet(cb);
+}
diff --git a/components/streams/src/sender.cpp b/components/streams/src/sender.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..275ad63f0cabc538004492dbd31e6e7c33acf040
--- /dev/null
+++ b/components/streams/src/sender.cpp
@@ -0,0 +1,186 @@
+#include <ftl/streams/sender.hpp>
+
+#include "injectors.hpp"
+
+using ftl::stream::Sender;
+using ftl::codecs::StreamPacket;
+using ftl::codecs::Packet;
+using ftl::codecs::Channels;
+using ftl::codecs::Channel;
+using ftl::codecs::definition_t;
+using ftl::codecs::device_t;
+using ftl::codecs::codec_t;
+using ftl::stream::injectCalibration;
+using ftl::stream::injectPose;
+using ftl::stream::injectConfig;
+
+Sender::Sender(nlohmann::json &config) : ftl::Configurable(config), stream_(nullptr) {
+	//do_inject_ = false;
+}
+
+Sender::~Sender() {
+    // Delete all encoders
+}
+
+/*void Sender::onStateChange(const std::function<void(ftl::codecs::Channel,const ftl::rgbd::FrameState&)> &cb) {
+	if (cb && state_cb_) throw ftl::exception("State change callback already set");
+	state_cb_ = cb;
+}*/
+
+void Sender::setStream(ftl::stream::Stream*s) {
+	if (stream_) stream_->onPacket(nullptr);
+    stream_ = s;
+	stream_->onPacket([this](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+		LOG(INFO) << "SENDER REQUEST : " << (int)spkt.channel;
+
+		//if (state_cb_) state_cb_(spkt.channel, spkt.streamID, spkt.frame_number);
+
+		// Inject state packets
+		//do_inject_ = true;
+		do_inject_.clear();
+	});
+}
+
+template <typename T>
+static void writeValue(std::vector<unsigned char> &data, T value) {
+	unsigned char *pvalue_start = (unsigned char*)&value;
+	data.insert(data.end(), pvalue_start, pvalue_start+sizeof(T));
+}
+
+static void mergeNALUnits(const std::list<ftl::codecs::Packet> &pkts, ftl::codecs::Packet &pkt) {
+	size_t size = 0;
+	for (auto i=pkts.begin(); i!=pkts.end(); ++i) size += (*i).data.size();
+
+	// TODO: Check Codec, jpg etc can just use last frame.
+	// TODO: Also if iframe, can just use that instead
+
+	const auto &first = pkts.front();
+	pkt.codec = first.codec;
+	pkt.definition = first.definition;
+	pkt.frame_count = first.frame_count;
+	pkt.bitrate = first.bitrate;
+	pkt.flags = first.flags | ftl::codecs::kFlagMultiple;  // means merged packets
+	pkt.data.reserve(size+pkts.size()*sizeof(int));
+
+	for (auto i=pkts.begin(); i!=pkts.end(); ++i) {
+		writeValue<int>(pkt.data, (*i).data.size());
+		//LOG(INFO) << "NAL Count = " << (*i).data.size();
+		pkt.data.insert(pkt.data.end(), (*i).data.begin(), (*i).data.end());
+	}
+}
+
+void Sender::post(const ftl::rgbd::FrameSet &fs) {
+    if (!stream_) return;
+
+    Channels selected;
+	if (stream_->size() > 0) selected = stream_->selected(0);
+
+	Channels available;  // but not selected and actually sent.
+
+	bool do_inject = !do_inject_.test_and_set();
+	//do_inject_ = false;
+
+    for (int i=0; i<fs.frames.size(); ++i) {
+        const auto &frame = fs.frames[i];
+
+		if (do_inject) {
+			//LOG(INFO) << "Force inject calibration";
+			injectCalibration(stream_, fs, i);
+			injectCalibration(stream_, fs, i, true);
+			injectPose(stream_, fs, i);
+			injectConfig(stream_, fs, i);
+		} else {
+			if (frame.hasChanged(Channel::Pose)) injectPose(stream_, fs, i);
+			if (frame.hasChanged(Channel::Calibration)) injectCalibration(stream_, fs, i);
+			if (frame.hasChanged(Channel::Calibration2)) injectCalibration(stream_, fs, i, true);
+			if (frame.hasChanged(Channel::Configuration)) injectConfig(stream_, fs, i);
+		}
+
+        for (auto c : frame.getChannels()) {
+			if (selected.has(c)) {
+				// FIXME: Sends high res colour, but receive end currently broken
+				//auto cc = (c == Channel::Colour && frame.hasChannel(Channel::ColourHighRes)) ? Channel::ColourHighRes : Channel::Colour;
+				auto cc = c;
+
+				StreamPacket spkt;
+				spkt.version = 4;
+				spkt.timestamp = fs.timestamp;
+				spkt.streamID = 0; //fs.id;
+				spkt.frame_number = i;
+				spkt.channel = c;
+
+				// Check if there are existing encoded packets
+				const auto &packets = frame.getPackets(cc);
+				if (packets.size() > 0) {
+					if (packets.size() > 1) {
+						LOG(WARNING) << "Multi-packet send";
+						ftl::codecs::Packet pkt;
+						mergeNALUnits(packets, pkt);
+						stream_->post(spkt, pkt);
+					} else {
+						// Send existing encoding instead of re-encoding
+						//for (auto &pkt : packets) {
+						stream_->post(spkt, packets.front());
+						//}
+					}
+				} else  {
+					auto *enc = _getEncoder(fs.id, i, cc);
+
+					if (enc) {
+						// FIXME: Timestamps may not always be aligned to interval.
+						//if (do_inject || fs.timestamp % (10*ftl::timer::getInterval()) == 0) enc->reset();
+						if (do_inject) enc->reset();
+						try {
+							enc->encode(frame.get<cv::cuda::GpuMat>(cc), 0, [this,&spkt](const ftl::codecs::Packet &pkt){
+								stream_->post(spkt, pkt);
+							});
+						} catch (std::exception &e) {
+							LOG(ERROR) << "Exception in encoder: " << e.what();
+						}
+					} else {
+						LOG(ERROR) << "No encoder";
+					}
+				}
+			} else {
+				available += c;
+			}
+        }
+    }
+
+	for (auto c : available) {
+		// Not selected so send an empty packet...
+		StreamPacket spkt;
+		spkt.version = 4;
+		spkt.timestamp = fs.timestamp;
+		spkt.streamID = 0; // FIXME: fs.id;
+		spkt.frame_number = 255;
+		spkt.channel = c;
+
+		Packet pkt;
+		pkt.codec = codec_t::Any;
+		pkt.definition = definition_t::Any;
+		pkt.frame_count = 1;
+		pkt.bitrate = 0;
+		stream_->post(spkt, pkt);
+	}
+
+	//do_inject_ = false;
+}
+
+ftl::codecs::Encoder *Sender::_getEncoder(int fsid, int fid, Channel c) {
+	int id = (fsid << 16) | (fid << 8) | (int)c;
+
+	{
+		SHARED_LOCK(mutex_, lk);
+		auto i = encoders_.find(id);
+		if (i != encoders_.end()) {
+			return (*i).second;
+		}
+	}
+
+	auto *enc = ftl::codecs::allocateEncoder(
+					definition_t::HD1080, device_t::Any, codec_t::Any);
+	UNIQUE_LOCK(mutex_, lk);
+	encoders_[id] = enc;
+	return enc;
+}
diff --git a/components/streams/src/stream.cpp b/components/streams/src/stream.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4a01287f479bd815c4a1456940ac211b9d58069b
--- /dev/null
+++ b/components/streams/src/stream.cpp
@@ -0,0 +1,246 @@
+#include <ftl/streams/stream.hpp>
+
+using ftl::stream::Muxer;
+using ftl::stream::Broadcast;
+using ftl::stream::Intercept;
+using ftl::stream::Stream;
+
+const ftl::codecs::Channels<0> &Stream::available(int fs) const {
+	SHARED_LOCK(mtx_, lk);
+	if (fs < 0 || fs >= state_.size()) throw ftl::exception("Frameset index out-of-bounds");
+	return state_[fs].available;
+}
+
+const ftl::codecs::Channels<0> &Stream::selected(int fs) const {
+	SHARED_LOCK(mtx_, lk);
+	if (fs < 0 || fs >= state_.size()) throw ftl::exception("Frameset index out-of-bounds");
+	return state_[fs].selected;
+}
+
+void Stream::select(int fs, const ftl::codecs::Channels<0> &s) {
+	UNIQUE_LOCK(mtx_, lk);
+	if (fs < 0 || fs >= state_.size()) throw ftl::exception("Frameset index out-of-bounds");
+	state_[fs].selected = s;
+}
+
+ftl::codecs::Channels<0> &Stream::available(int fs) {
+	UNIQUE_LOCK(mtx_, lk);
+	if (fs < 0) throw ftl::exception("Frameset index out-of-bounds");
+	if (fs >= state_.size()) state_.resize(fs+1);
+	return state_[fs].available;
+}
+
+// ==== Muxer ==================================================================
+
+Muxer::Muxer(nlohmann::json &config) : Stream(config), nid_(0) {
+
+}
+
+Muxer::~Muxer() {
+
+}
+
+
+void Muxer::add(Stream *s) {
+	UNIQUE_LOCK(mutex_,lk);
+
+	auto &se = streams_.emplace_back();
+	int i = streams_.size()-1;
+	se.stream = s;
+
+	s->onPacket([this,s,i](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+		//SHARED_LOCK(mutex_, lk);
+		int id = _lookup(i, spkt.frame_number);
+		
+		ftl::codecs::StreamPacket spkt2 = spkt;
+		spkt2.streamID = 0;
+		spkt2.frame_number = id;
+		_notify(spkt2, pkt);
+		s->select(spkt.streamID, selected(0));
+	});
+}
+
+bool Muxer::onPacket(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &cb) {
+	UNIQUE_LOCK(mutex_,lk);
+	cb_ = cb;
+	return true;
+}
+
+bool Muxer::post(const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+	SHARED_LOCK(mutex_, lk);
+	available(spkt.frameSetID()) += spkt.channel;
+	
+	if (spkt.frame_number < revmap_.size()) {
+		auto [sid, ssid] = revmap_[spkt.frame_number];
+		auto &se = streams_[sid];
+
+		ftl::codecs::StreamPacket spkt2 = spkt;
+		spkt2.streamID = 0;
+		spkt2.frame_number = ssid;
+		se.stream->select(0, selected(spkt.frameSetID()));
+		return se.stream->post(spkt2, pkt);
+	} else {
+		return false;
+	}
+}
+
+bool Muxer::begin() {
+	bool r = true;
+	for (auto &s : streams_) {
+		r = r && s.stream->begin();
+	}
+	return r;
+}
+
+bool Muxer::end() {
+	bool r = true;
+	for (auto &s : streams_) {
+		r = r && s.stream->end();
+	}
+	return r;
+}
+
+bool Muxer::active() {
+	bool r = true;
+	for (auto &s : streams_) {
+		r = r && s.stream->active();
+	}
+	return r;
+}
+
+int Muxer::_lookup(int sid, int ssid) {
+	SHARED_LOCK(mutex_, lk);
+	auto &se = streams_[sid];
+	if (ssid >= se.maps.size()) {
+		lk.unlock();
+		{
+			UNIQUE_LOCK(mutex_, lk2);
+			int nid = nid_++;
+			se.maps.push_back(nid);
+			revmap_.push_back({sid,ssid});
+		}
+		lk.lock();
+	}
+	return se.maps[ssid];
+}
+
+void Muxer::_notify(const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+	SHARED_LOCK(mutex_, lk);
+	available(spkt.frameSetID()) += spkt.channel;
+	if (cb_) cb_(spkt, pkt);
+}
+
+// ==== Broadcaster ============================================================
+
+Broadcast::Broadcast(nlohmann::json &config) : Stream(config) {
+
+}
+
+Broadcast::~Broadcast() {
+
+}
+
+void Broadcast::add(Stream *s) {
+	UNIQUE_LOCK(mutex_,lk);
+
+	streams_.push_back(s);
+	s->onPacket([this](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+		SHARED_LOCK(mutex_, lk);
+		if (cb_) cb_(spkt, pkt);
+	});
+}
+
+bool Broadcast::onPacket(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &cb) {
+	UNIQUE_LOCK(mutex_,lk);
+	cb_ = cb;
+	return true;
+}
+
+bool Broadcast::post(const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+	SHARED_LOCK(mutex_, lk);
+	
+	bool status = true;
+	for (auto s : streams_) {
+		status = status && s->post(spkt, pkt);
+	}
+	return status;
+}
+
+bool Broadcast::begin() {
+	bool r = true;
+	for (auto &s : streams_) {
+		r = r && s->begin();
+	}
+	return r;
+}
+
+bool Broadcast::end() {
+	bool r = true;
+	for (auto &s : streams_) {
+		r = r && s->end();
+	}
+	return r;
+}
+
+bool Broadcast::active() {
+	bool r = true;
+	for (auto &s : streams_) {
+		r = r && s->active();
+	}
+	return r;
+}
+
+// ==== Intercept ==============================================================
+
+Intercept::Intercept(nlohmann::json &config) : Stream(config) {
+	stream_ = nullptr;
+}
+
+Intercept::~Intercept() {
+
+}
+
+void Intercept::setStream(Stream *s) {
+	UNIQUE_LOCK(mutex_,lk);
+
+	stream_ = s;
+	s->onPacket([this](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+		SHARED_LOCK(mutex_, lk);
+		available(spkt.frameSetID()) += spkt.channel;
+		if (cb_) cb_(spkt, pkt);
+		if (intercept_) intercept_(spkt, pkt);
+		stream_->select(spkt.streamID, selected(spkt.streamID));
+	});
+}
+
+bool Intercept::onPacket(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &cb) {
+	UNIQUE_LOCK(mutex_,lk);
+	cb_ = cb;
+	return true;
+}
+
+bool Intercept::onIntercept(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &cb) {
+	UNIQUE_LOCK(mutex_,lk);
+	intercept_ = cb;
+	return true;
+}
+
+bool Intercept::post(const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+	SHARED_LOCK(mutex_, lk);
+	available(spkt.frameSetID()) += spkt.channel;
+	stream_->select(spkt.streamID, selected(spkt.streamID));
+	//if (intercept_) intercept_(spkt, pkt);
+	return stream_->post(spkt, pkt);
+}
+
+bool Intercept::begin() {
+	return stream_->begin();
+}
+
+bool Intercept::end() {
+	return stream_->end();
+}
+
+bool Intercept::active() {
+	return stream_->active();
+}
diff --git a/components/streams/test/CMakeLists.txt b/components/streams/test/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6fec3245c6eae652c93a68549bbedc513334212d
--- /dev/null
+++ b/components/streams/test/CMakeLists.txt
@@ -0,0 +1,36 @@
+### Stream Unit ################################################################
+add_executable(stream_unit
+	./tests.cpp
+	./stream_unit.cpp
+	../src/stream.cpp
+)
+target_include_directories(stream_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
+target_link_libraries(stream_unit
+	ftlcommon ftlcodecs ftlrgbd)
+
+add_test(StreamUnitTest stream_unit)
+
+### File Stream Unit ###########################################################
+add_executable(filestream_unit
+	./tests.cpp
+	./filestream_unit.cpp
+	../src/filestream.cpp
+	../src/stream.cpp
+)
+target_include_directories(filestream_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
+target_link_libraries(filestream_unit
+	ftlcommon ftlcodecs ftlrgbd)
+
+add_test(FileStreamUnitTest filestream_unit)
+
+### Net Stream Unit ###########################################################
+#add_executable(netstream_unit
+#	./tests.cpp
+#	./netstream_unit.cpp
+#	../src/stream.cpp
+#)
+#target_include_directories(netstream_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include")
+#target_link_libraries(netstream_unit
+#	ftlcommon ftlcodecs ftlrgbd ftlnet)
+
+#add_test(NetStreamUnitTest netstream_unit)
diff --git a/components/streams/test/catch.hpp b/components/streams/test/catch.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b1b2411d24885571e21ec4b3653af58c57011c3e
--- /dev/null
+++ b/components/streams/test/catch.hpp
@@ -0,0 +1,14362 @@
+/*
+ *  Catch v2.5.0
+ *  Generated: 2018-11-26 20:46:12.165372
+ *  ----------------------------------------------------------
+ *  This file has been merged from multiple headers. Please don't edit it directly
+ *  Copyright (c) 2018 Two Blue Cubes Ltd. All rights reserved.
+ *
+ *  Distributed under the Boost Software License, Version 1.0. (See accompanying
+ *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ */
+#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
+#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
+// start catch.hpp
+
+
+#define CATCH_VERSION_MAJOR 2
+#define CATCH_VERSION_MINOR 5
+#define CATCH_VERSION_PATCH 0
+
+#ifdef __clang__
+#    pragma clang system_header
+#elif defined __GNUC__
+#    pragma GCC system_header
+#endif
+
+// start catch_suppress_warnings.h
+
+#ifdef __clang__
+#   ifdef __ICC // icpc defines the __clang__ macro
+#       pragma warning(push)
+#       pragma warning(disable: 161 1682)
+#   else // __ICC
+#       pragma clang diagnostic push
+#       pragma clang diagnostic ignored "-Wpadded"
+#       pragma clang diagnostic ignored "-Wswitch-enum"
+#       pragma clang diagnostic ignored "-Wcovered-switch-default"
+#    endif
+#elif defined __GNUC__
+     // GCC likes to warn on REQUIREs, and we cannot suppress them
+     // locally because g++'s support for _Pragma is lacking in older,
+     // still supported, versions
+#    pragma GCC diagnostic ignored "-Wparentheses"
+#    pragma GCC diagnostic push
+#    pragma GCC diagnostic ignored "-Wunused-variable"
+#    pragma GCC diagnostic ignored "-Wpadded"
+#endif
+// end catch_suppress_warnings.h
+#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER)
+#  define CATCH_IMPL
+#  define CATCH_CONFIG_ALL_PARTS
+#endif
+
+// In the impl file, we want to have access to all parts of the headers
+// Can also be used to sanely support PCHs
+#if defined(CATCH_CONFIG_ALL_PARTS)
+#  define CATCH_CONFIG_EXTERNAL_INTERFACES
+#  if defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#    undef CATCH_CONFIG_DISABLE_MATCHERS
+#  endif
+#  if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER)
+#    define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
+#  endif
+#endif
+
+#if !defined(CATCH_CONFIG_IMPL_ONLY)
+// start catch_platform.h
+
+#ifdef __APPLE__
+# include <TargetConditionals.h>
+# if TARGET_OS_OSX == 1
+#  define CATCH_PLATFORM_MAC
+# elif TARGET_OS_IPHONE == 1
+#  define CATCH_PLATFORM_IPHONE
+# endif
+
+#elif defined(linux) || defined(__linux) || defined(__linux__)
+#  define CATCH_PLATFORM_LINUX
+
+#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__)
+#  define CATCH_PLATFORM_WINDOWS
+#endif
+
+// end catch_platform.h
+
+#ifdef CATCH_IMPL
+#  ifndef CLARA_CONFIG_MAIN
+#    define CLARA_CONFIG_MAIN_NOT_DEFINED
+#    define CLARA_CONFIG_MAIN
+#  endif
+#endif
+
+// start catch_user_interfaces.h
+
+namespace Catch {
+    unsigned int rngSeed();
+}
+
+// end catch_user_interfaces.h
+// start catch_tag_alias_autoregistrar.h
+
+// start catch_common.h
+
+// start catch_compiler_capabilities.h
+
+// Detect a number of compiler features - by compiler
+// The following features are defined:
+//
+// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported?
+// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported?
+// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported?
+// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled?
+// ****************
+// Note to maintainers: if new toggles are added please document them
+// in configuration.md, too
+// ****************
+
+// In general each macro has a _NO_<feature name> form
+// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature.
+// Many features, at point of detection, define an _INTERNAL_ macro, so they
+// can be combined, en-mass, with the _NO_ forms later.
+
+#ifdef __cplusplus
+
+#  if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L)
+#    define CATCH_CPP14_OR_GREATER
+#  endif
+
+#  if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
+#    define CATCH_CPP17_OR_GREATER
+#  endif
+
+#endif
+
+#if defined(CATCH_CPP17_OR_GREATER)
+#  define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS
+#endif
+
+#ifdef __clang__
+
+#       define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+            _Pragma( "clang diagnostic push" ) \
+            _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \
+            _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"")
+#       define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \
+            _Pragma( "clang diagnostic pop" )
+
+#       define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
+            _Pragma( "clang diagnostic push" ) \
+            _Pragma( "clang diagnostic ignored \"-Wparentheses\"" )
+#       define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \
+            _Pragma( "clang diagnostic pop" )
+
+#       define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \
+            _Pragma( "clang diagnostic push" ) \
+            _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" )
+#       define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS \
+            _Pragma( "clang diagnostic pop" )
+
+#endif // __clang__
+
+////////////////////////////////////////////////////////////////////////////////
+// Assume that non-Windows platforms support posix signals by default
+#if !defined(CATCH_PLATFORM_WINDOWS)
+    #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// We know some environments not to support full POSIX signals
+#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__)
+    #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS
+#endif
+
+#ifdef __OS400__
+#       define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS
+#       define CATCH_CONFIG_COLOUR_NONE
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Android somehow still does not support std::to_string
+#if defined(__ANDROID__)
+#    define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Not all Windows environments support SEH properly
+#if defined(__MINGW32__)
+#    define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// PS4
+#if defined(__ORBIS__)
+#    define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Cygwin
+#ifdef __CYGWIN__
+
+// Required for some versions of Cygwin to declare gettimeofday
+// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin
+#   define _BSD_SOURCE
+// some versions of cygwin (most) do not support std::to_string. Use the libstd check.
+// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813
+# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \
+	       && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF))
+
+#	define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING
+
+# endif
+#endif // __CYGWIN__
+
+////////////////////////////////////////////////////////////////////////////////
+// Visual C++
+#ifdef _MSC_VER
+
+#  if _MSC_VER >= 1900 // Visual Studio 2015 or newer
+#    define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS
+#  endif
+
+// Universal Windows platform does not support SEH
+// Or console colours (or console at all...)
+#  if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
+#    define CATCH_CONFIG_COLOUR_NONE
+#  else
+#    define CATCH_INTERNAL_CONFIG_WINDOWS_SEH
+#  endif
+
+// MSVC traditional preprocessor needs some workaround for __VA_ARGS__
+// _MSVC_TRADITIONAL == 0 means new conformant preprocessor
+// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor
+#  if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL)
+#    define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+#  endif
+
+#endif // _MSC_VER
+
+////////////////////////////////////////////////////////////////////////////////
+// Check if we are compiled with -fno-exceptions or equivalent
+#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND)
+#  define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// DJGPP
+#ifdef __DJGPP__
+#  define CATCH_INTERNAL_CONFIG_NO_WCHAR
+#endif // __DJGPP__
+
+////////////////////////////////////////////////////////////////////////////////
+// Embarcadero C++Build
+#if defined(__BORLANDC__)
+    #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Use of __COUNTER__ is suppressed during code analysis in
+// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly
+// handled by it.
+// Otherwise all supported compilers support COUNTER macro,
+// but user still might want to turn it off
+#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L )
+    #define CATCH_INTERNAL_CONFIG_COUNTER
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Check if string_view is available and usable
+// The check is split apart to work around v140 (VS2015) preprocessor issue...
+#if defined(__has_include)
+#if __has_include(<string_view>) && defined(CATCH_CPP17_OR_GREATER)
+#    define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW
+#endif
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Check if variant is available and usable
+#if defined(__has_include)
+#  if __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER)
+#    if defined(__clang__) && (__clang_major__ < 8)
+       // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852
+       // fix should be in clang 8, workaround in libstdc++ 8.2
+#      include <ciso646>
+#      if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9)
+#        define CATCH_CONFIG_NO_CPP17_VARIANT
+#     else
+#        define CATCH_INTERNAL_CONFIG_CPP17_VARIANT
+#      endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9)
+#    endif // defined(__clang__) && (__clang_major__ < 8)
+#  endif // __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER)
+#endif // __has_include
+
+#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER)
+#   define CATCH_CONFIG_COUNTER
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH)
+#   define CATCH_CONFIG_WINDOWS_SEH
+#endif
+// This is set by default, because we assume that unix compilers are posix-signal-compatible by default.
+#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS)
+#   define CATCH_CONFIG_POSIX_SIGNALS
+#endif
+// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions.
+#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR)
+#   define CATCH_CONFIG_WCHAR
+#endif
+
+#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING)
+#    define CATCH_CONFIG_CPP11_TO_STRING
+#endif
+
+#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)
+#  define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS
+#endif
+
+#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW)
+#  define CATCH_CONFIG_CPP17_STRING_VIEW
+#endif
+
+#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT)
+#  define CATCH_CONFIG_CPP17_VARIANT
+#endif
+
+#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT)
+#  define CATCH_INTERNAL_CONFIG_NEW_CAPTURE
+#endif
+
+#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE)
+#  define CATCH_CONFIG_NEW_CAPTURE
+#endif
+
+#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
+#  define CATCH_CONFIG_DISABLE_EXCEPTIONS
+#endif
+
+#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN)
+#  define CATCH_CONFIG_POLYFILL_ISNAN
+#endif
+
+#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS)
+#   define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS
+#   define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS
+#endif
+#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS)
+#   define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
+#   define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS
+#endif
+#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS)
+#   define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS
+#   define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS
+#endif
+
+#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
+#define CATCH_TRY if ((true))
+#define CATCH_CATCH_ALL if ((false))
+#define CATCH_CATCH_ANON(type) if ((false))
+#else
+#define CATCH_TRY try
+#define CATCH_CATCH_ALL catch (...)
+#define CATCH_CATCH_ANON(type) catch (type)
+#endif
+
+#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR)
+#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+#endif
+
+// end catch_compiler_capabilities.h
+#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line
+#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line )
+#ifdef CATCH_CONFIG_COUNTER
+#  define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ )
+#else
+#  define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ )
+#endif
+
+#include <iosfwd>
+#include <string>
+#include <cstdint>
+
+// We need a dummy global operator<< so we can bring it into Catch namespace later
+struct Catch_global_namespace_dummy {};
+std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy);
+
+namespace Catch {
+
+    struct CaseSensitive { enum Choice {
+        Yes,
+        No
+    }; };
+
+    class NonCopyable {
+        NonCopyable( NonCopyable const& )              = delete;
+        NonCopyable( NonCopyable && )                  = delete;
+        NonCopyable& operator = ( NonCopyable const& ) = delete;
+        NonCopyable& operator = ( NonCopyable && )     = delete;
+
+    protected:
+        NonCopyable();
+        virtual ~NonCopyable();
+    };
+
+    struct SourceLineInfo {
+
+        SourceLineInfo() = delete;
+        SourceLineInfo( char const* _file, std::size_t _line ) noexcept
+        :   file( _file ),
+            line( _line )
+        {}
+
+        SourceLineInfo( SourceLineInfo const& other )        = default;
+        SourceLineInfo( SourceLineInfo && )                  = default;
+        SourceLineInfo& operator = ( SourceLineInfo const& ) = default;
+        SourceLineInfo& operator = ( SourceLineInfo && )     = default;
+
+        bool empty() const noexcept;
+        bool operator == ( SourceLineInfo const& other ) const noexcept;
+        bool operator < ( SourceLineInfo const& other ) const noexcept;
+
+        char const* file;
+        std::size_t line;
+    };
+
+    std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info );
+
+    // Bring in operator<< from global namespace into Catch namespace
+    // This is necessary because the overload of operator<< above makes
+    // lookup stop at namespace Catch
+    using ::operator<<;
+
+    // Use this in variadic streaming macros to allow
+    //    >> +StreamEndStop
+    // as well as
+    //    >> stuff +StreamEndStop
+    struct StreamEndStop {
+        std::string operator+() const;
+    };
+    template<typename T>
+    T const& operator + ( T const& value, StreamEndStop ) {
+        return value;
+    }
+}
+
+#define CATCH_INTERNAL_LINEINFO \
+    ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) )
+
+// end catch_common.h
+namespace Catch {
+
+    struct RegistrarForTagAliases {
+        RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo );
+    };
+
+} // end namespace Catch
+
+#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \
+    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+    namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \
+    CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS
+
+// end catch_tag_alias_autoregistrar.h
+// start catch_test_registry.h
+
+// start catch_interfaces_testcase.h
+
+#include <vector>
+#include <memory>
+
+namespace Catch {
+
+    class TestSpec;
+
+    struct ITestInvoker {
+        virtual void invoke () const = 0;
+        virtual ~ITestInvoker();
+    };
+
+    using ITestCasePtr = std::shared_ptr<ITestInvoker>;
+
+    class TestCase;
+    struct IConfig;
+
+    struct ITestCaseRegistry {
+        virtual ~ITestCaseRegistry();
+        virtual std::vector<TestCase> const& getAllTests() const = 0;
+        virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const = 0;
+    };
+
+    bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config );
+    std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config );
+    std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config );
+
+}
+
+// end catch_interfaces_testcase.h
+// start catch_stringref.h
+
+#include <cstddef>
+#include <string>
+#include <iosfwd>
+
+namespace Catch {
+
+    class StringData;
+
+    /// A non-owning string class (similar to the forthcoming std::string_view)
+    /// Note that, because a StringRef may be a substring of another string,
+    /// it may not be null terminated. c_str() must return a null terminated
+    /// string, however, and so the StringRef will internally take ownership
+    /// (taking a copy), if necessary. In theory this ownership is not externally
+    /// visible - but it does mean (substring) StringRefs should not be shared between
+    /// threads.
+    class StringRef {
+    public:
+        using size_type = std::size_t;
+
+    private:
+        friend struct StringRefTestAccess;
+
+        char const* m_start;
+        size_type m_size;
+
+        char* m_data = nullptr;
+
+        void takeOwnership();
+
+        static constexpr char const* const s_empty = "";
+
+    public: // construction/ assignment
+        StringRef() noexcept
+        :   StringRef( s_empty, 0 )
+        {}
+
+        StringRef( StringRef const& other ) noexcept
+        :   m_start( other.m_start ),
+            m_size( other.m_size )
+        {}
+
+        StringRef( StringRef&& other ) noexcept
+        :   m_start( other.m_start ),
+            m_size( other.m_size ),
+            m_data( other.m_data )
+        {
+            other.m_data = nullptr;
+        }
+
+        StringRef( char const* rawChars ) noexcept;
+
+        StringRef( char const* rawChars, size_type size ) noexcept
+        :   m_start( rawChars ),
+            m_size( size )
+        {}
+
+        StringRef( std::string const& stdString ) noexcept
+        :   m_start( stdString.c_str() ),
+            m_size( stdString.size() )
+        {}
+
+        ~StringRef() noexcept {
+            delete[] m_data;
+        }
+
+        auto operator = ( StringRef const &other ) noexcept -> StringRef& {
+            delete[] m_data;
+            m_data = nullptr;
+            m_start = other.m_start;
+            m_size = other.m_size;
+            return *this;
+        }
+
+        operator std::string() const;
+
+        void swap( StringRef& other ) noexcept;
+
+    public: // operators
+        auto operator == ( StringRef const& other ) const noexcept -> bool;
+        auto operator != ( StringRef const& other ) const noexcept -> bool;
+
+        auto operator[] ( size_type index ) const noexcept -> char;
+
+    public: // named queries
+        auto empty() const noexcept -> bool {
+            return m_size == 0;
+        }
+        auto size() const noexcept -> size_type {
+            return m_size;
+        }
+
+        auto numberOfCharacters() const noexcept -> size_type;
+        auto c_str() const -> char const*;
+
+    public: // substrings and searches
+        auto substr( size_type start, size_type size ) const noexcept -> StringRef;
+
+        // Returns the current start pointer.
+        // Note that the pointer can change when if the StringRef is a substring
+        auto currentData() const noexcept -> char const*;
+
+    private: // ownership queries - may not be consistent between calls
+        auto isOwned() const noexcept -> bool;
+        auto isSubstring() const noexcept -> bool;
+    };
+
+    auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string;
+    auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string;
+    auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string;
+
+    auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&;
+    auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&;
+
+    inline auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef {
+        return StringRef( rawChars, size );
+    }
+
+} // namespace Catch
+
+inline auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef {
+    return Catch::StringRef( rawChars, size );
+}
+
+// end catch_stringref.h
+// start catch_type_traits.hpp
+
+
+namespace Catch{
+
+#ifdef CATCH_CPP17_OR_GREATER
+	template <typename...>
+	inline constexpr auto is_unique = std::true_type{};
+
+	template <typename T, typename... Rest>
+	inline constexpr auto is_unique<T, Rest...> = std::bool_constant<
+		(!std::is_same_v<T, Rest> && ...) && is_unique<Rest...>
+	>{};
+#else
+
+template <typename...>
+struct is_unique : std::true_type{};
+
+template <typename T0, typename T1, typename... Rest>
+struct is_unique<T0, T1, Rest...> : std::integral_constant
+<bool,
+     !std::is_same<T0, T1>::value
+     && is_unique<T0, Rest...>::value
+     && is_unique<T1, Rest...>::value
+>{};
+
+#endif
+}
+
+// end catch_type_traits.hpp
+// start catch_preprocessor.hpp
+
+
+#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__
+#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__)))
+#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__)))
+#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__)))
+#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__)))
+#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__)))
+
+#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__
+// MSVC needs more evaluations
+#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__)))
+#define CATCH_RECURSE(...)  CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__))
+#else
+#define CATCH_RECURSE(...)  CATCH_RECURSION_LEVEL5(__VA_ARGS__)
+#endif
+
+#define CATCH_REC_END(...)
+#define CATCH_REC_OUT
+
+#define CATCH_EMPTY()
+#define CATCH_DEFER(id) id CATCH_EMPTY()
+
+#define CATCH_REC_GET_END2() 0, CATCH_REC_END
+#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2
+#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1
+#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT
+#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0)
+#define CATCH_REC_NEXT(test, next)  CATCH_REC_NEXT1(CATCH_REC_GET_END test, next)
+
+#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ )
+#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ )
+#define CATCH_REC_LIST2(f, x, peek, ...)   f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ )
+
+#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ )
+#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ )
+#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...)   f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ )
+
+// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results,
+// and passes userdata as the first parameter to each invocation,
+// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c)
+#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
+
+#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
+
+#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param)
+#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__
+#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__
+#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF
+
+#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__)
+
+#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name, __VA_ARGS__)
+#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name,...) Name " - " #__VA_ARGS__
+#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME(Name,...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))
+#else
+// MSVC is adding extra space and needs more calls to properly remove ()
+#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name,...) Name " -" #__VA_ARGS__
+#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME1(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, __VA_ARGS__)
+#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME1(Name, INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)))
+#endif
+
+// end catch_preprocessor.hpp
+namespace Catch {
+
+template<typename C>
+class TestInvokerAsMethod : public ITestInvoker {
+    void (C::*m_testAsMethod)();
+public:
+    TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {}
+
+    void invoke() const override {
+        C obj;
+        (obj.*m_testAsMethod)();
+    }
+};
+
+auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker*;
+
+template<typename C>
+auto makeTestInvoker( void (C::*testAsMethod)() ) noexcept -> ITestInvoker* {
+    return new(std::nothrow) TestInvokerAsMethod<C>( testAsMethod );
+}
+
+struct NameAndTags {
+    NameAndTags( StringRef const& name_ = StringRef(), StringRef const& tags_ = StringRef() ) noexcept;
+    StringRef name;
+    StringRef tags;
+};
+
+struct AutoReg : NonCopyable {
+    AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept;
+    ~AutoReg();
+};
+
+} // end namespace Catch
+
+#if defined(CATCH_CONFIG_DISABLE)
+    #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \
+        static void TestName()
+    #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \
+        namespace{                        \
+            struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \
+                void test();              \
+            };                            \
+        }                                 \
+        void TestName::test()
+    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION( TestName, ... )  \
+        template<typename TestType>                                             \
+        static void TestName()
+    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... )    \
+        namespace{                                                                                  \
+            template<typename TestType>                                                             \
+            struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) {     \
+                void test();                                                                        \
+            };                                                                                      \
+        }                                                                                           \
+        template<typename TestType>                                                                 \
+        void TestName::test()
+#endif
+
+    ///////////////////////////////////////////////////////////////////////////////
+    #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \
+        static void TestName(); \
+        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \
+        CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \
+        static void TestName()
+    #define INTERNAL_CATCH_TESTCASE( ... ) \
+        INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ )
+
+    ///////////////////////////////////////////////////////////////////////////////
+    #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \
+        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &QualifiedMethod ), CATCH_INTERNAL_LINEINFO, "&" #QualifiedMethod, Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \
+        CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS
+
+    ///////////////////////////////////////////////////////////////////////////////
+    #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\
+        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+        namespace{ \
+            struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \
+                void test(); \
+            }; \
+            Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \
+        } \
+        CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \
+        void TestName::test()
+    #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \
+        INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ )
+
+    ///////////////////////////////////////////////////////////////////////////////
+    #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \
+        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+        Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \
+        CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS
+
+    ///////////////////////////////////////////////////////////////////////////////
+    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(TestName, TestFunc, Name, Tags, ... )\
+        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+        template<typename TestType> \
+        static void TestFunc();\
+        namespace {\
+            template<typename...Types> \
+            struct TestName{\
+                template<typename...Ts> \
+                TestName(Ts...names){\
+                    CATCH_INTERNAL_CHECK_UNIQUE_TYPES(CATCH_REC_LIST(INTERNAL_CATCH_REMOVE_PARENS, __VA_ARGS__)) \
+                    using expander = int[];\
+                    (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ names, Tags } ), 0)... };/* NOLINT */ \
+                }\
+            };\
+            INTERNAL_CATCH_TEMPLATE_REGISTRY_INITIATE(TestName, Name, __VA_ARGS__) \
+        }\
+        CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \
+        template<typename TestType> \
+        static void TestFunc()
+
+#if defined(CATCH_CPP17_OR_GREATER)
+#define CATCH_INTERNAL_CHECK_UNIQUE_TYPES(...) static_assert(Catch::is_unique<__VA_ARGS__>,"Duplicate type detected in declaration of template test case");
+#else
+#define CATCH_INTERNAL_CHECK_UNIQUE_TYPES(...) static_assert(Catch::is_unique<__VA_ARGS__>::value,"Duplicate type detected in declaration of template test case");
+#endif
+
+#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \
+        INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, __VA_ARGS__ )
+#else
+    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \
+        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, __VA_ARGS__ ) )
+#endif
+
+    #define INTERNAL_CATCH_TEMPLATE_REGISTRY_INITIATE(TestName, Name, ...)\
+        static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\
+            TestName<CATCH_REC_LIST(INTERNAL_CATCH_REMOVE_PARENS, __VA_ARGS__)>(CATCH_REC_LIST_UD(INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME,Name, __VA_ARGS__));\
+            return 0;\
+        }();
+
+    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, ... ) \
+        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+        namespace{ \
+            template<typename TestType> \
+            struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \
+                void test();\
+            };\
+            template<typename...Types> \
+            struct TestNameClass{\
+                template<typename...Ts> \
+                TestNameClass(Ts...names){\
+                    CATCH_INTERNAL_CHECK_UNIQUE_TYPES(CATCH_REC_LIST(INTERNAL_CATCH_REMOVE_PARENS, __VA_ARGS__)) \
+                    using expander = int[];\
+                    (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ names, Tags } ), 0)... };/* NOLINT */ \
+                }\
+            };\
+            INTERNAL_CATCH_TEMPLATE_REGISTRY_INITIATE(TestNameClass, Name, __VA_ARGS__)\
+        }\
+        CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS\
+        template<typename TestType> \
+        void TestName<TestType>::test()
+
+#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \
+        INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, __VA_ARGS__ )
+#else
+    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \
+        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, __VA_ARGS__ ) )
+#endif
+
+// end catch_test_registry.h
+// start catch_capture.hpp
+
+// start catch_assertionhandler.h
+
+// start catch_assertioninfo.h
+
+// start catch_result_type.h
+
+namespace Catch {
+
+    // ResultWas::OfType enum
+    struct ResultWas { enum OfType {
+        Unknown = -1,
+        Ok = 0,
+        Info = 1,
+        Warning = 2,
+
+        FailureBit = 0x10,
+
+        ExpressionFailed = FailureBit | 1,
+        ExplicitFailure = FailureBit | 2,
+
+        Exception = 0x100 | FailureBit,
+
+        ThrewException = Exception | 1,
+        DidntThrowException = Exception | 2,
+
+        FatalErrorCondition = 0x200 | FailureBit
+
+    }; };
+
+    bool isOk( ResultWas::OfType resultType );
+    bool isJustInfo( int flags );
+
+    // ResultDisposition::Flags enum
+    struct ResultDisposition { enum Flags {
+        Normal = 0x01,
+
+        ContinueOnFailure = 0x02,   // Failures fail test, but execution continues
+        FalseTest = 0x04,           // Prefix expression with !
+        SuppressFail = 0x08         // Failures are reported but do not fail the test
+    }; };
+
+    ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs );
+
+    bool shouldContinueOnFailure( int flags );
+    inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; }
+    bool shouldSuppressFailure( int flags );
+
+} // end namespace Catch
+
+// end catch_result_type.h
+namespace Catch {
+
+    struct AssertionInfo
+    {
+        StringRef macroName;
+        SourceLineInfo lineInfo;
+        StringRef capturedExpression;
+        ResultDisposition::Flags resultDisposition;
+
+        // We want to delete this constructor but a compiler bug in 4.8 means
+        // the struct is then treated as non-aggregate
+        //AssertionInfo() = delete;
+    };
+
+} // end namespace Catch
+
+// end catch_assertioninfo.h
+// start catch_decomposer.h
+
+// start catch_tostring.h
+
+#include <vector>
+#include <cstddef>
+#include <type_traits>
+#include <string>
+// start catch_stream.h
+
+#include <iosfwd>
+#include <cstddef>
+#include <ostream>
+
+namespace Catch {
+
+    std::ostream& cout();
+    std::ostream& cerr();
+    std::ostream& clog();
+
+    class StringRef;
+
+    struct IStream {
+        virtual ~IStream();
+        virtual std::ostream& stream() const = 0;
+    };
+
+    auto makeStream( StringRef const &filename ) -> IStream const*;
+
+    class ReusableStringStream {
+        std::size_t m_index;
+        std::ostream* m_oss;
+    public:
+        ReusableStringStream();
+        ~ReusableStringStream();
+
+        auto str() const -> std::string;
+
+        template<typename T>
+        auto operator << ( T const& value ) -> ReusableStringStream& {
+            *m_oss << value;
+            return *this;
+        }
+        auto get() -> std::ostream& { return *m_oss; }
+    };
+}
+
+// end catch_stream.h
+
+#ifdef CATCH_CONFIG_CPP17_STRING_VIEW
+#include <string_view>
+#endif
+
+#ifdef __OBJC__
+// start catch_objc_arc.hpp
+
+#import <Foundation/Foundation.h>
+
+#ifdef __has_feature
+#define CATCH_ARC_ENABLED __has_feature(objc_arc)
+#else
+#define CATCH_ARC_ENABLED 0
+#endif
+
+void arcSafeRelease( NSObject* obj );
+id performOptionalSelector( id obj, SEL sel );
+
+#if !CATCH_ARC_ENABLED
+inline void arcSafeRelease( NSObject* obj ) {
+    [obj release];
+}
+inline id performOptionalSelector( id obj, SEL sel ) {
+    if( [obj respondsToSelector: sel] )
+        return [obj performSelector: sel];
+    return nil;
+}
+#define CATCH_UNSAFE_UNRETAINED
+#define CATCH_ARC_STRONG
+#else
+inline void arcSafeRelease( NSObject* ){}
+inline id performOptionalSelector( id obj, SEL sel ) {
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
+#endif
+    if( [obj respondsToSelector: sel] )
+        return [obj performSelector: sel];
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+    return nil;
+}
+#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained
+#define CATCH_ARC_STRONG __strong
+#endif
+
+// end catch_objc_arc.hpp
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless
+#endif
+
+namespace Catch {
+    namespace Detail {
+
+        extern const std::string unprintableString;
+
+        std::string rawMemoryToString( const void *object, std::size_t size );
+
+        template<typename T>
+        std::string rawMemoryToString( const T& object ) {
+          return rawMemoryToString( &object, sizeof(object) );
+        }
+
+        template<typename T>
+        class IsStreamInsertable {
+            template<typename SS, typename TT>
+            static auto test(int)
+                -> decltype(std::declval<SS&>() << std::declval<TT>(), std::true_type());
+
+            template<typename, typename>
+            static auto test(...)->std::false_type;
+
+        public:
+            static const bool value = decltype(test<std::ostream, const T&>(0))::value;
+        };
+
+        template<typename E>
+        std::string convertUnknownEnumToString( E e );
+
+        template<typename T>
+        typename std::enable_if<
+            !std::is_enum<T>::value && !std::is_base_of<std::exception, T>::value,
+        std::string>::type convertUnstreamable( T const& ) {
+            return Detail::unprintableString;
+        }
+        template<typename T>
+        typename std::enable_if<
+            !std::is_enum<T>::value && std::is_base_of<std::exception, T>::value,
+         std::string>::type convertUnstreamable(T const& ex) {
+            return ex.what();
+        }
+
+        template<typename T>
+        typename std::enable_if<
+            std::is_enum<T>::value
+        , std::string>::type convertUnstreamable( T const& value ) {
+            return convertUnknownEnumToString( value );
+        }
+
+#if defined(_MANAGED)
+        //! Convert a CLR string to a utf8 std::string
+        template<typename T>
+        std::string clrReferenceToString( T^ ref ) {
+            if (ref == nullptr)
+                return std::string("null");
+            auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString());
+            cli::pin_ptr<System::Byte> p = &bytes[0];
+            return std::string(reinterpret_cast<char const *>(p), bytes->Length);
+        }
+#endif
+
+    } // namespace Detail
+
+    // If we decide for C++14, change these to enable_if_ts
+    template <typename T, typename = void>
+    struct StringMaker {
+        template <typename Fake = T>
+        static
+        typename std::enable_if<::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type
+            convert(const Fake& value) {
+                ReusableStringStream rss;
+                // NB: call using the function-like syntax to avoid ambiguity with
+                // user-defined templated operator<< under clang.
+                rss.operator<<(value);
+                return rss.str();
+        }
+
+        template <typename Fake = T>
+        static
+        typename std::enable_if<!::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type
+            convert( const Fake& value ) {
+#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER)
+            return Detail::convertUnstreamable(value);
+#else
+            return CATCH_CONFIG_FALLBACK_STRINGIFIER(value);
+#endif
+        }
+    };
+
+    namespace Detail {
+
+        // This function dispatches all stringification requests inside of Catch.
+        // Should be preferably called fully qualified, like ::Catch::Detail::stringify
+        template <typename T>
+        std::string stringify(const T& e) {
+            return ::Catch::StringMaker<typename std::remove_cv<typename std::remove_reference<T>::type>::type>::convert(e);
+        }
+
+        template<typename E>
+        std::string convertUnknownEnumToString( E e ) {
+            return ::Catch::Detail::stringify(static_cast<typename std::underlying_type<E>::type>(e));
+        }
+
+#if defined(_MANAGED)
+        template <typename T>
+        std::string stringify( T^ e ) {
+            return ::Catch::StringMaker<T^>::convert(e);
+        }
+#endif
+
+    } // namespace Detail
+
+    // Some predefined specializations
+
+    template<>
+    struct StringMaker<std::string> {
+        static std::string convert(const std::string& str);
+    };
+
+#ifdef CATCH_CONFIG_CPP17_STRING_VIEW
+    template<>
+    struct StringMaker<std::string_view> {
+        static std::string convert(std::string_view str);
+    };
+#endif
+
+    template<>
+    struct StringMaker<char const *> {
+        static std::string convert(char const * str);
+    };
+    template<>
+    struct StringMaker<char *> {
+        static std::string convert(char * str);
+    };
+
+#ifdef CATCH_CONFIG_WCHAR
+    template<>
+    struct StringMaker<std::wstring> {
+        static std::string convert(const std::wstring& wstr);
+    };
+
+# ifdef CATCH_CONFIG_CPP17_STRING_VIEW
+    template<>
+    struct StringMaker<std::wstring_view> {
+        static std::string convert(std::wstring_view str);
+    };
+# endif
+
+    template<>
+    struct StringMaker<wchar_t const *> {
+        static std::string convert(wchar_t const * str);
+    };
+    template<>
+    struct StringMaker<wchar_t *> {
+        static std::string convert(wchar_t * str);
+    };
+#endif
+
+    // TBD: Should we use `strnlen` to ensure that we don't go out of the buffer,
+    //      while keeping string semantics?
+    template<int SZ>
+    struct StringMaker<char[SZ]> {
+        static std::string convert(char const* str) {
+            return ::Catch::Detail::stringify(std::string{ str });
+        }
+    };
+    template<int SZ>
+    struct StringMaker<signed char[SZ]> {
+        static std::string convert(signed char const* str) {
+            return ::Catch::Detail::stringify(std::string{ reinterpret_cast<char const *>(str) });
+        }
+    };
+    template<int SZ>
+    struct StringMaker<unsigned char[SZ]> {
+        static std::string convert(unsigned char const* str) {
+            return ::Catch::Detail::stringify(std::string{ reinterpret_cast<char const *>(str) });
+        }
+    };
+
+    template<>
+    struct StringMaker<int> {
+        static std::string convert(int value);
+    };
+    template<>
+    struct StringMaker<long> {
+        static std::string convert(long value);
+    };
+    template<>
+    struct StringMaker<long long> {
+        static std::string convert(long long value);
+    };
+    template<>
+    struct StringMaker<unsigned int> {
+        static std::string convert(unsigned int value);
+    };
+    template<>
+    struct StringMaker<unsigned long> {
+        static std::string convert(unsigned long value);
+    };
+    template<>
+    struct StringMaker<unsigned long long> {
+        static std::string convert(unsigned long long value);
+    };
+
+    template<>
+    struct StringMaker<bool> {
+        static std::string convert(bool b);
+    };
+
+    template<>
+    struct StringMaker<char> {
+        static std::string convert(char c);
+    };
+    template<>
+    struct StringMaker<signed char> {
+        static std::string convert(signed char c);
+    };
+    template<>
+    struct StringMaker<unsigned char> {
+        static std::string convert(unsigned char c);
+    };
+
+    template<>
+    struct StringMaker<std::nullptr_t> {
+        static std::string convert(std::nullptr_t);
+    };
+
+    template<>
+    struct StringMaker<float> {
+        static std::string convert(float value);
+    };
+    template<>
+    struct StringMaker<double> {
+        static std::string convert(double value);
+    };
+
+    template <typename T>
+    struct StringMaker<T*> {
+        template <typename U>
+        static std::string convert(U* p) {
+            if (p) {
+                return ::Catch::Detail::rawMemoryToString(p);
+            } else {
+                return "nullptr";
+            }
+        }
+    };
+
+    template <typename R, typename C>
+    struct StringMaker<R C::*> {
+        static std::string convert(R C::* p) {
+            if (p) {
+                return ::Catch::Detail::rawMemoryToString(p);
+            } else {
+                return "nullptr";
+            }
+        }
+    };
+
+#if defined(_MANAGED)
+    template <typename T>
+    struct StringMaker<T^> {
+        static std::string convert( T^ ref ) {
+            return ::Catch::Detail::clrReferenceToString(ref);
+        }
+    };
+#endif
+
+    namespace Detail {
+        template<typename InputIterator>
+        std::string rangeToString(InputIterator first, InputIterator last) {
+            ReusableStringStream rss;
+            rss << "{ ";
+            if (first != last) {
+                rss << ::Catch::Detail::stringify(*first);
+                for (++first; first != last; ++first)
+                    rss << ", " << ::Catch::Detail::stringify(*first);
+            }
+            rss << " }";
+            return rss.str();
+        }
+    }
+
+#ifdef __OBJC__
+    template<>
+    struct StringMaker<NSString*> {
+        static std::string convert(NSString * nsstring) {
+            if (!nsstring)
+                return "nil";
+            return std::string("@") + [nsstring UTF8String];
+        }
+    };
+    template<>
+    struct StringMaker<NSObject*> {
+        static std::string convert(NSObject* nsObject) {
+            return ::Catch::Detail::stringify([nsObject description]);
+        }
+
+    };
+    namespace Detail {
+        inline std::string stringify( NSString* nsstring ) {
+            return StringMaker<NSString*>::convert( nsstring );
+        }
+
+    } // namespace Detail
+#endif // __OBJC__
+
+} // namespace Catch
+
+//////////////////////////////////////////////////////
+// Separate std-lib types stringification, so it can be selectively enabled
+// This means that we do not bring in
+
+#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS)
+#  define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER
+#  define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER
+#  define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER
+#  define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
+#endif
+
+// Separate std::pair specialization
+#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER)
+#include <utility>
+namespace Catch {
+    template<typename T1, typename T2>
+    struct StringMaker<std::pair<T1, T2> > {
+        static std::string convert(const std::pair<T1, T2>& pair) {
+            ReusableStringStream rss;
+            rss << "{ "
+                << ::Catch::Detail::stringify(pair.first)
+                << ", "
+                << ::Catch::Detail::stringify(pair.second)
+                << " }";
+            return rss.str();
+        }
+    };
+}
+#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER
+
+// Separate std::tuple specialization
+#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER)
+#include <tuple>
+namespace Catch {
+    namespace Detail {
+        template<
+            typename Tuple,
+            std::size_t N = 0,
+            bool = (N < std::tuple_size<Tuple>::value)
+            >
+            struct TupleElementPrinter {
+            static void print(const Tuple& tuple, std::ostream& os) {
+                os << (N ? ", " : " ")
+                    << ::Catch::Detail::stringify(std::get<N>(tuple));
+                TupleElementPrinter<Tuple, N + 1>::print(tuple, os);
+            }
+        };
+
+        template<
+            typename Tuple,
+            std::size_t N
+        >
+            struct TupleElementPrinter<Tuple, N, false> {
+            static void print(const Tuple&, std::ostream&) {}
+        };
+
+    }
+
+    template<typename ...Types>
+    struct StringMaker<std::tuple<Types...>> {
+        static std::string convert(const std::tuple<Types...>& tuple) {
+            ReusableStringStream rss;
+            rss << '{';
+            Detail::TupleElementPrinter<std::tuple<Types...>>::print(tuple, rss.get());
+            rss << " }";
+            return rss.str();
+        }
+    };
+}
+#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER
+
+#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT)
+#include <variant>
+namespace Catch {
+    template<>
+    struct StringMaker<std::monostate> {
+        static std::string convert(const std::monostate&) {
+            return "{ }";
+        }
+    };
+
+    template<typename... Elements>
+    struct StringMaker<std::variant<Elements...>> {
+        static std::string convert(const std::variant<Elements...>& variant) {
+            if (variant.valueless_by_exception()) {
+                return "{valueless variant}";
+            } else {
+                return std::visit(
+                    [](const auto& value) {
+                        return ::Catch::Detail::stringify(value);
+                    },
+                    variant
+                );
+            }
+        }
+    };
+}
+#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER
+
+namespace Catch {
+    struct not_this_one {}; // Tag type for detecting which begin/ end are being selected
+
+    // Import begin/ end from std here so they are considered alongside the fallback (...) overloads in this namespace
+    using std::begin;
+    using std::end;
+
+    not_this_one begin( ... );
+    not_this_one end( ... );
+
+    template <typename T>
+    struct is_range {
+        static const bool value =
+            !std::is_same<decltype(begin(std::declval<T>())), not_this_one>::value &&
+            !std::is_same<decltype(end(std::declval<T>())), not_this_one>::value;
+    };
+
+#if defined(_MANAGED) // Managed types are never ranges
+    template <typename T>
+    struct is_range<T^> {
+        static const bool value = false;
+    };
+#endif
+
+    template<typename Range>
+    std::string rangeToString( Range const& range ) {
+        return ::Catch::Detail::rangeToString( begin( range ), end( range ) );
+    }
+
+    // Handle vector<bool> specially
+    template<typename Allocator>
+    std::string rangeToString( std::vector<bool, Allocator> const& v ) {
+        ReusableStringStream rss;
+        rss << "{ ";
+        bool first = true;
+        for( bool b : v ) {
+            if( first )
+                first = false;
+            else
+                rss << ", ";
+            rss << ::Catch::Detail::stringify( b );
+        }
+        rss << " }";
+        return rss.str();
+    }
+
+    template<typename R>
+    struct StringMaker<R, typename std::enable_if<is_range<R>::value && !::Catch::Detail::IsStreamInsertable<R>::value>::type> {
+        static std::string convert( R const& range ) {
+            return rangeToString( range );
+        }
+    };
+
+    template <typename T, int SZ>
+    struct StringMaker<T[SZ]> {
+        static std::string convert(T const(&arr)[SZ]) {
+            return rangeToString(arr);
+        }
+    };
+
+} // namespace Catch
+
+// Separate std::chrono::duration specialization
+#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER)
+#include <ctime>
+#include <ratio>
+#include <chrono>
+
+namespace Catch {
+
+template <class Ratio>
+struct ratio_string {
+    static std::string symbol();
+};
+
+template <class Ratio>
+std::string ratio_string<Ratio>::symbol() {
+    Catch::ReusableStringStream rss;
+    rss << '[' << Ratio::num << '/'
+        << Ratio::den << ']';
+    return rss.str();
+}
+template <>
+struct ratio_string<std::atto> {
+    static std::string symbol();
+};
+template <>
+struct ratio_string<std::femto> {
+    static std::string symbol();
+};
+template <>
+struct ratio_string<std::pico> {
+    static std::string symbol();
+};
+template <>
+struct ratio_string<std::nano> {
+    static std::string symbol();
+};
+template <>
+struct ratio_string<std::micro> {
+    static std::string symbol();
+};
+template <>
+struct ratio_string<std::milli> {
+    static std::string symbol();
+};
+
+    ////////////
+    // std::chrono::duration specializations
+    template<typename Value, typename Ratio>
+    struct StringMaker<std::chrono::duration<Value, Ratio>> {
+        static std::string convert(std::chrono::duration<Value, Ratio> const& duration) {
+            ReusableStringStream rss;
+            rss << duration.count() << ' ' << ratio_string<Ratio>::symbol() << 's';
+            return rss.str();
+        }
+    };
+    template<typename Value>
+    struct StringMaker<std::chrono::duration<Value, std::ratio<1>>> {
+        static std::string convert(std::chrono::duration<Value, std::ratio<1>> const& duration) {
+            ReusableStringStream rss;
+            rss << duration.count() << " s";
+            return rss.str();
+        }
+    };
+    template<typename Value>
+    struct StringMaker<std::chrono::duration<Value, std::ratio<60>>> {
+        static std::string convert(std::chrono::duration<Value, std::ratio<60>> const& duration) {
+            ReusableStringStream rss;
+            rss << duration.count() << " m";
+            return rss.str();
+        }
+    };
+    template<typename Value>
+    struct StringMaker<std::chrono::duration<Value, std::ratio<3600>>> {
+        static std::string convert(std::chrono::duration<Value, std::ratio<3600>> const& duration) {
+            ReusableStringStream rss;
+            rss << duration.count() << " h";
+            return rss.str();
+        }
+    };
+
+    ////////////
+    // std::chrono::time_point specialization
+    // Generic time_point cannot be specialized, only std::chrono::time_point<system_clock>
+    template<typename Clock, typename Duration>
+    struct StringMaker<std::chrono::time_point<Clock, Duration>> {
+        static std::string convert(std::chrono::time_point<Clock, Duration> const& time_point) {
+            return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch";
+        }
+    };
+    // std::chrono::time_point<system_clock> specialization
+    template<typename Duration>
+    struct StringMaker<std::chrono::time_point<std::chrono::system_clock, Duration>> {
+        static std::string convert(std::chrono::time_point<std::chrono::system_clock, Duration> const& time_point) {
+            auto converted = std::chrono::system_clock::to_time_t(time_point);
+
+#ifdef _MSC_VER
+            std::tm timeInfo = {};
+            gmtime_s(&timeInfo, &converted);
+#else
+            std::tm* timeInfo = std::gmtime(&converted);
+#endif
+
+            auto const timeStampSize = sizeof("2017-01-16T17:06:45Z");
+            char timeStamp[timeStampSize];
+            const char * const fmt = "%Y-%m-%dT%H:%M:%SZ";
+
+#ifdef _MSC_VER
+            std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);
+#else
+            std::strftime(timeStamp, timeStampSize, fmt, timeInfo);
+#endif
+            return std::string(timeStamp);
+        }
+    };
+}
+#endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+// end catch_tostring.h
+#include <iosfwd>
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4389) // '==' : signed/unsigned mismatch
+#pragma warning(disable:4018) // more "signed/unsigned mismatch"
+#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform)
+#pragma warning(disable:4180) // qualifier applied to function type has no meaning
+#endif
+
+namespace Catch {
+
+    struct ITransientExpression {
+        auto isBinaryExpression() const -> bool { return m_isBinaryExpression; }
+        auto getResult() const -> bool { return m_result; }
+        virtual void streamReconstructedExpression( std::ostream &os ) const = 0;
+
+        ITransientExpression( bool isBinaryExpression, bool result )
+        :   m_isBinaryExpression( isBinaryExpression ),
+            m_result( result )
+        {}
+
+        // We don't actually need a virtual destructor, but many static analysers
+        // complain if it's not here :-(
+        virtual ~ITransientExpression();
+
+        bool m_isBinaryExpression;
+        bool m_result;
+
+    };
+
+    void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs );
+
+    template<typename LhsT, typename RhsT>
+    class BinaryExpr  : public ITransientExpression {
+        LhsT m_lhs;
+        StringRef m_op;
+        RhsT m_rhs;
+
+        void streamReconstructedExpression( std::ostream &os ) const override {
+            formatReconstructedExpression
+                    ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) );
+        }
+
+    public:
+        BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs )
+        :   ITransientExpression{ true, comparisonResult },
+            m_lhs( lhs ),
+            m_op( op ),
+            m_rhs( rhs )
+        {}
+    };
+
+    template<typename LhsT>
+    class UnaryExpr : public ITransientExpression {
+        LhsT m_lhs;
+
+        void streamReconstructedExpression( std::ostream &os ) const override {
+            os << Catch::Detail::stringify( m_lhs );
+        }
+
+    public:
+        explicit UnaryExpr( LhsT lhs )
+        :   ITransientExpression{ false, lhs ? true : false },
+            m_lhs( lhs )
+        {}
+    };
+
+    // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int)
+    template<typename LhsT, typename RhsT>
+    auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return static_cast<bool>(lhs == rhs); }
+    template<typename T>
+    auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); }
+    template<typename T>
+    auto compareEqual( T* const& lhs, long rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); }
+    template<typename T>
+    auto compareEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; }
+    template<typename T>
+    auto compareEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; }
+
+    template<typename LhsT, typename RhsT>
+    auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return static_cast<bool>(lhs != rhs); }
+    template<typename T>
+    auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); }
+    template<typename T>
+    auto compareNotEqual( T* const& lhs, long rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); }
+    template<typename T>
+    auto compareNotEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; }
+    template<typename T>
+    auto compareNotEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; }
+
+    template<typename LhsT>
+    class ExprLhs {
+        LhsT m_lhs;
+    public:
+        explicit ExprLhs( LhsT lhs ) : m_lhs( lhs ) {}
+
+        template<typename RhsT>
+        auto operator == ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
+            return { compareEqual( m_lhs, rhs ), m_lhs, "==", rhs };
+        }
+        auto operator == ( bool rhs ) -> BinaryExpr<LhsT, bool> const {
+            return { m_lhs == rhs, m_lhs, "==", rhs };
+        }
+
+        template<typename RhsT>
+        auto operator != ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
+            return { compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs };
+        }
+        auto operator != ( bool rhs ) -> BinaryExpr<LhsT, bool> const {
+            return { m_lhs != rhs, m_lhs, "!=", rhs };
+        }
+
+        template<typename RhsT>
+        auto operator > ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
+            return { static_cast<bool>(m_lhs > rhs), m_lhs, ">", rhs };
+        }
+        template<typename RhsT>
+        auto operator < ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
+            return { static_cast<bool>(m_lhs < rhs), m_lhs, "<", rhs };
+        }
+        template<typename RhsT>
+        auto operator >= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
+            return { static_cast<bool>(m_lhs >= rhs), m_lhs, ">=", rhs };
+        }
+        template<typename RhsT>
+        auto operator <= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
+            return { static_cast<bool>(m_lhs <= rhs), m_lhs, "<=", rhs };
+        }
+
+        auto makeUnaryExpr() const -> UnaryExpr<LhsT> {
+            return UnaryExpr<LhsT>{ m_lhs };
+        }
+    };
+
+    void handleExpression( ITransientExpression const& expr );
+
+    template<typename T>
+    void handleExpression( ExprLhs<T> const& expr ) {
+        handleExpression( expr.makeUnaryExpr() );
+    }
+
+    struct Decomposer {
+        template<typename T>
+        auto operator <= ( T const& lhs ) -> ExprLhs<T const&> {
+            return ExprLhs<T const&>{ lhs };
+        }
+
+        auto operator <=( bool value ) -> ExprLhs<bool> {
+            return ExprLhs<bool>{ value };
+        }
+    };
+
+} // end namespace Catch
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+// end catch_decomposer.h
+// start catch_interfaces_capture.h
+
+#include <string>
+
+namespace Catch {
+
+    class AssertionResult;
+    struct AssertionInfo;
+    struct SectionInfo;
+    struct SectionEndInfo;
+    struct MessageInfo;
+    struct Counts;
+    struct BenchmarkInfo;
+    struct BenchmarkStats;
+    struct AssertionReaction;
+    struct SourceLineInfo;
+
+    struct ITransientExpression;
+    struct IGeneratorTracker;
+
+    struct IResultCapture {
+
+        virtual ~IResultCapture();
+
+        virtual bool sectionStarted(    SectionInfo const& sectionInfo,
+                                        Counts& assertions ) = 0;
+        virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0;
+        virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0;
+
+        virtual auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& = 0;
+
+        virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0;
+        virtual void benchmarkEnded( BenchmarkStats const& stats ) = 0;
+
+        virtual void pushScopedMessage( MessageInfo const& message ) = 0;
+        virtual void popScopedMessage( MessageInfo const& message ) = 0;
+
+        virtual void handleFatalErrorCondition( StringRef message ) = 0;
+
+        virtual void handleExpr
+                (   AssertionInfo const& info,
+                    ITransientExpression const& expr,
+                    AssertionReaction& reaction ) = 0;
+        virtual void handleMessage
+                (   AssertionInfo const& info,
+                    ResultWas::OfType resultType,
+                    StringRef const& message,
+                    AssertionReaction& reaction ) = 0;
+        virtual void handleUnexpectedExceptionNotThrown
+                (   AssertionInfo const& info,
+                    AssertionReaction& reaction ) = 0;
+        virtual void handleUnexpectedInflightException
+                (   AssertionInfo const& info,
+                    std::string const& message,
+                    AssertionReaction& reaction ) = 0;
+        virtual void handleIncomplete
+                (   AssertionInfo const& info ) = 0;
+        virtual void handleNonExpr
+                (   AssertionInfo const &info,
+                    ResultWas::OfType resultType,
+                    AssertionReaction &reaction ) = 0;
+
+        virtual bool lastAssertionPassed() = 0;
+        virtual void assertionPassed() = 0;
+
+        // Deprecated, do not use:
+        virtual std::string getCurrentTestName() const = 0;
+        virtual const AssertionResult* getLastResult() const = 0;
+        virtual void exceptionEarlyReported() = 0;
+    };
+
+    IResultCapture& getResultCapture();
+}
+
+// end catch_interfaces_capture.h
+namespace Catch {
+
+    struct TestFailureException{};
+    struct AssertionResultData;
+    struct IResultCapture;
+    class RunContext;
+
+    class LazyExpression {
+        friend class AssertionHandler;
+        friend struct AssertionStats;
+        friend class RunContext;
+
+        ITransientExpression const* m_transientExpression = nullptr;
+        bool m_isNegated;
+    public:
+        LazyExpression( bool isNegated );
+        LazyExpression( LazyExpression const& other );
+        LazyExpression& operator = ( LazyExpression const& ) = delete;
+
+        explicit operator bool() const;
+
+        friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&;
+    };
+
+    struct AssertionReaction {
+        bool shouldDebugBreak = false;
+        bool shouldThrow = false;
+    };
+
+    class AssertionHandler {
+        AssertionInfo m_assertionInfo;
+        AssertionReaction m_reaction;
+        bool m_completed = false;
+        IResultCapture& m_resultCapture;
+
+    public:
+        AssertionHandler
+            (   StringRef const& macroName,
+                SourceLineInfo const& lineInfo,
+                StringRef capturedExpression,
+                ResultDisposition::Flags resultDisposition );
+        ~AssertionHandler() {
+            if ( !m_completed ) {
+                m_resultCapture.handleIncomplete( m_assertionInfo );
+            }
+        }
+
+        template<typename T>
+        void handleExpr( ExprLhs<T> const& expr ) {
+            handleExpr( expr.makeUnaryExpr() );
+        }
+        void handleExpr( ITransientExpression const& expr );
+
+        void handleMessage(ResultWas::OfType resultType, StringRef const& message);
+
+        void handleExceptionThrownAsExpected();
+        void handleUnexpectedExceptionNotThrown();
+        void handleExceptionNotThrownAsExpected();
+        void handleThrowingCallSkipped();
+        void handleUnexpectedInflightException();
+
+        void complete();
+        void setCompleted();
+
+        // query
+        auto allowThrows() const -> bool;
+    };
+
+    void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString );
+
+} // namespace Catch
+
+// end catch_assertionhandler.h
+// start catch_message.h
+
+#include <string>
+#include <vector>
+
+namespace Catch {
+
+    struct MessageInfo {
+        MessageInfo(    StringRef const& _macroName,
+                        SourceLineInfo const& _lineInfo,
+                        ResultWas::OfType _type );
+
+        StringRef macroName;
+        std::string message;
+        SourceLineInfo lineInfo;
+        ResultWas::OfType type;
+        unsigned int sequence;
+
+        bool operator == ( MessageInfo const& other ) const;
+        bool operator < ( MessageInfo const& other ) const;
+    private:
+        static unsigned int globalCount;
+    };
+
+    struct MessageStream {
+
+        template<typename T>
+        MessageStream& operator << ( T const& value ) {
+            m_stream << value;
+            return *this;
+        }
+
+        ReusableStringStream m_stream;
+    };
+
+    struct MessageBuilder : MessageStream {
+        MessageBuilder( StringRef const& macroName,
+                        SourceLineInfo const& lineInfo,
+                        ResultWas::OfType type );
+
+        template<typename T>
+        MessageBuilder& operator << ( T const& value ) {
+            m_stream << value;
+            return *this;
+        }
+
+        MessageInfo m_info;
+    };
+
+    class ScopedMessage {
+    public:
+        explicit ScopedMessage( MessageBuilder const& builder );
+        ~ScopedMessage();
+
+        MessageInfo m_info;
+    };
+
+    class Capturer {
+        std::vector<MessageInfo> m_messages;
+        IResultCapture& m_resultCapture = getResultCapture();
+        size_t m_captured = 0;
+    public:
+        Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names );
+        ~Capturer();
+
+        void captureValue( size_t index, std::string const& value );
+
+        template<typename T>
+        void captureValues( size_t index, T const& value ) {
+            captureValue( index, Catch::Detail::stringify( value ) );
+        }
+
+        template<typename T, typename... Ts>
+        void captureValues( size_t index, T const& value, Ts const&... values ) {
+            captureValue( index, Catch::Detail::stringify(value) );
+            captureValues( index+1, values... );
+        }
+    };
+
+} // end namespace Catch
+
+// end catch_message.h
+#if !defined(CATCH_CONFIG_DISABLE)
+
+#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION)
+  #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__
+#else
+  #define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION"
+#endif
+
+#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
+
+///////////////////////////////////////////////////////////////////////////////
+// Another way to speed-up compilation is to omit local try-catch for REQUIRE*
+// macros.
+#define INTERNAL_CATCH_TRY
+#define INTERNAL_CATCH_CATCH( capturer )
+
+#else // CATCH_CONFIG_FAST_COMPILE
+
+#define INTERNAL_CATCH_TRY try
+#define INTERNAL_CATCH_CATCH( handler ) catch(...) { handler.handleUnexpectedInflightException(); }
+
+#endif
+
+#define INTERNAL_CATCH_REACT( handler ) handler.complete();
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \
+    do { \
+        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \
+        INTERNAL_CATCH_TRY { \
+            CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
+            catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); \
+            CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \
+        } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \
+        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
+    } while( (void)0, false && static_cast<bool>( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look
+    // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&.
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \
+    INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \
+    if( Catch::getResultCapture().lastAssertionPassed() )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \
+    INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \
+    if( !Catch::getResultCapture().lastAssertionPassed() )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \
+    do { \
+        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \
+        try { \
+            static_cast<void>(__VA_ARGS__); \
+            catchAssertionHandler.handleExceptionNotThrownAsExpected(); \
+        } \
+        catch( ... ) { \
+            catchAssertionHandler.handleUnexpectedInflightException(); \
+        } \
+        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
+    } while( false )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \
+    do { \
+        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \
+        if( catchAssertionHandler.allowThrows() ) \
+            try { \
+                static_cast<void>(__VA_ARGS__); \
+                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \
+            } \
+            catch( ... ) { \
+                catchAssertionHandler.handleExceptionThrownAsExpected(); \
+            } \
+        else \
+            catchAssertionHandler.handleThrowingCallSkipped(); \
+        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
+    } while( false )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \
+    do { \
+        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \
+        if( catchAssertionHandler.allowThrows() ) \
+            try { \
+                static_cast<void>(expr); \
+                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \
+            } \
+            catch( exceptionType const& ) { \
+                catchAssertionHandler.handleExceptionThrownAsExpected(); \
+            } \
+            catch( ... ) { \
+                catchAssertionHandler.handleUnexpectedInflightException(); \
+            } \
+        else \
+            catchAssertionHandler.handleThrowingCallSkipped(); \
+        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
+    } while( false )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \
+    do { \
+        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \
+        catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \
+        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
+    } while( false )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \
+    auto varName = Catch::Capturer( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info, #__VA_ARGS__ ); \
+    varName.captureValues( 0, __VA_ARGS__ )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_INFO( macroName, log ) \
+    Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log );
+
+///////////////////////////////////////////////////////////////////////////////
+// Although this is matcher-based, it can be used with just a string
+#define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \
+    do { \
+        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \
+        if( catchAssertionHandler.allowThrows() ) \
+            try { \
+                static_cast<void>(__VA_ARGS__); \
+                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \
+            } \
+            catch( ... ) { \
+                Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher##_catch_sr ); \
+            } \
+        else \
+            catchAssertionHandler.handleThrowingCallSkipped(); \
+        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
+    } while( false )
+
+#endif // CATCH_CONFIG_DISABLE
+
+// end catch_capture.hpp
+// start catch_section.h
+
+// start catch_section_info.h
+
+// start catch_totals.h
+
+#include <cstddef>
+
+namespace Catch {
+
+    struct Counts {
+        Counts operator - ( Counts const& other ) const;
+        Counts& operator += ( Counts const& other );
+
+        std::size_t total() const;
+        bool allPassed() const;
+        bool allOk() const;
+
+        std::size_t passed = 0;
+        std::size_t failed = 0;
+        std::size_t failedButOk = 0;
+    };
+
+    struct Totals {
+
+        Totals operator - ( Totals const& other ) const;
+        Totals& operator += ( Totals const& other );
+
+        Totals delta( Totals const& prevTotals ) const;
+
+        int error = 0;
+        Counts assertions;
+        Counts testCases;
+    };
+}
+
+// end catch_totals.h
+#include <string>
+
+namespace Catch {
+
+    struct SectionInfo {
+        SectionInfo
+            (   SourceLineInfo const& _lineInfo,
+                std::string const& _name );
+
+        // Deprecated
+        SectionInfo
+            (   SourceLineInfo const& _lineInfo,
+                std::string const& _name,
+                std::string const& ) : SectionInfo( _lineInfo, _name ) {}
+
+        std::string name;
+        std::string description; // !Deprecated: this will always be empty
+        SourceLineInfo lineInfo;
+    };
+
+    struct SectionEndInfo {
+        SectionInfo sectionInfo;
+        Counts prevAssertions;
+        double durationInSeconds;
+    };
+
+} // end namespace Catch
+
+// end catch_section_info.h
+// start catch_timer.h
+
+#include <cstdint>
+
+namespace Catch {
+
+    auto getCurrentNanosecondsSinceEpoch() -> uint64_t;
+    auto getEstimatedClockResolution() -> uint64_t;
+
+    class Timer {
+        uint64_t m_nanoseconds = 0;
+    public:
+        void start();
+        auto getElapsedNanoseconds() const -> uint64_t;
+        auto getElapsedMicroseconds() const -> uint64_t;
+        auto getElapsedMilliseconds() const -> unsigned int;
+        auto getElapsedSeconds() const -> double;
+    };
+
+} // namespace Catch
+
+// end catch_timer.h
+#include <string>
+
+namespace Catch {
+
+    class Section : NonCopyable {
+    public:
+        Section( SectionInfo const& info );
+        ~Section();
+
+        // This indicates whether the section should be executed or not
+        explicit operator bool() const;
+
+    private:
+        SectionInfo m_info;
+
+        std::string m_name;
+        Counts m_assertions;
+        bool m_sectionIncluded;
+        Timer m_timer;
+    };
+
+} // end namespace Catch
+
+#define INTERNAL_CATCH_SECTION( ... ) \
+    CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \
+    if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \
+    CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS
+
+#define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \
+    CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \
+    if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, (Catch::ReusableStringStream() << __VA_ARGS__).str() ) ) \
+    CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS
+
+// end catch_section.h
+// start catch_benchmark.h
+
+#include <cstdint>
+#include <string>
+
+namespace Catch {
+
+    class BenchmarkLooper {
+
+        std::string m_name;
+        std::size_t m_count = 0;
+        std::size_t m_iterationsToRun = 1;
+        uint64_t m_resolution;
+        Timer m_timer;
+
+        static auto getResolution() -> uint64_t;
+    public:
+        // Keep most of this inline as it's on the code path that is being timed
+        BenchmarkLooper( StringRef name )
+        :   m_name( name ),
+            m_resolution( getResolution() )
+        {
+            reportStart();
+            m_timer.start();
+        }
+
+        explicit operator bool() {
+            if( m_count < m_iterationsToRun )
+                return true;
+            return needsMoreIterations();
+        }
+
+        void increment() {
+            ++m_count;
+        }
+
+        void reportStart();
+        auto needsMoreIterations() -> bool;
+    };
+
+} // end namespace Catch
+
+#define BENCHMARK( name ) \
+    for( Catch::BenchmarkLooper looper( name ); looper; looper.increment() )
+
+// end catch_benchmark.h
+// start catch_interfaces_exception.h
+
+// start catch_interfaces_registry_hub.h
+
+#include <string>
+#include <memory>
+
+namespace Catch {
+
+    class TestCase;
+    struct ITestCaseRegistry;
+    struct IExceptionTranslatorRegistry;
+    struct IExceptionTranslator;
+    struct IReporterRegistry;
+    struct IReporterFactory;
+    struct ITagAliasRegistry;
+    class StartupExceptionRegistry;
+
+    using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>;
+
+    struct IRegistryHub {
+        virtual ~IRegistryHub();
+
+        virtual IReporterRegistry const& getReporterRegistry() const = 0;
+        virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0;
+        virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0;
+
+        virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0;
+
+        virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0;
+    };
+
+    struct IMutableRegistryHub {
+        virtual ~IMutableRegistryHub();
+        virtual void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) = 0;
+        virtual void registerListener( IReporterFactoryPtr const& factory ) = 0;
+        virtual void registerTest( TestCase const& testInfo ) = 0;
+        virtual void registerTranslator( const IExceptionTranslator* translator ) = 0;
+        virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0;
+        virtual void registerStartupException() noexcept = 0;
+    };
+
+    IRegistryHub const& getRegistryHub();
+    IMutableRegistryHub& getMutableRegistryHub();
+    void cleanUp();
+    std::string translateActiveException();
+
+}
+
+// end catch_interfaces_registry_hub.h
+#if defined(CATCH_CONFIG_DISABLE)
+    #define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( translatorName, signature) \
+        static std::string translatorName( signature )
+#endif
+
+#include <exception>
+#include <string>
+#include <vector>
+
+namespace Catch {
+    using exceptionTranslateFunction = std::string(*)();
+
+    struct IExceptionTranslator;
+    using ExceptionTranslators = std::vector<std::unique_ptr<IExceptionTranslator const>>;
+
+    struct IExceptionTranslator {
+        virtual ~IExceptionTranslator();
+        virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0;
+    };
+
+    struct IExceptionTranslatorRegistry {
+        virtual ~IExceptionTranslatorRegistry();
+
+        virtual std::string translateActiveException() const = 0;
+    };
+
+    class ExceptionTranslatorRegistrar {
+        template<typename T>
+        class ExceptionTranslator : public IExceptionTranslator {
+        public:
+
+            ExceptionTranslator( std::string(*translateFunction)( T& ) )
+            : m_translateFunction( translateFunction )
+            {}
+
+            std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override {
+                try {
+                    if( it == itEnd )
+                        std::rethrow_exception(std::current_exception());
+                    else
+                        return (*it)->translate( it+1, itEnd );
+                }
+                catch( T& ex ) {
+                    return m_translateFunction( ex );
+                }
+            }
+
+        protected:
+            std::string(*m_translateFunction)( T& );
+        };
+
+    public:
+        template<typename T>
+        ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) {
+            getMutableRegistryHub().registerTranslator
+                ( new ExceptionTranslator<T>( translateFunction ) );
+        }
+    };
+}
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \
+    static std::string translatorName( signature ); \
+    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+    namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \
+    CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \
+    static std::string translatorName( signature )
+
+#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature )
+
+// end catch_interfaces_exception.h
+// start catch_approx.h
+
+#include <type_traits>
+
+namespace Catch {
+namespace Detail {
+
+    class Approx {
+    private:
+        bool equalityComparisonImpl(double other) const;
+        // Validates the new margin (margin >= 0)
+        // out-of-line to avoid including stdexcept in the header
+        void setMargin(double margin);
+        // Validates the new epsilon (0 < epsilon < 1)
+        // out-of-line to avoid including stdexcept in the header
+        void setEpsilon(double epsilon);
+
+    public:
+        explicit Approx ( double value );
+
+        static Approx custom();
+
+        Approx operator-() const;
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        Approx operator()( T const& value ) {
+            Approx approx( static_cast<double>(value) );
+            approx.m_epsilon = m_epsilon;
+            approx.m_margin = m_margin;
+            approx.m_scale = m_scale;
+            return approx;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        explicit Approx( T const& value ): Approx(static_cast<double>(value))
+        {}
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator == ( const T& lhs, Approx const& rhs ) {
+            auto lhs_v = static_cast<double>(lhs);
+            return rhs.equalityComparisonImpl(lhs_v);
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator == ( Approx const& lhs, const T& rhs ) {
+            return operator==( rhs, lhs );
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator != ( T const& lhs, Approx const& rhs ) {
+            return !operator==( lhs, rhs );
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator != ( Approx const& lhs, T const& rhs ) {
+            return !operator==( rhs, lhs );
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator <= ( T const& lhs, Approx const& rhs ) {
+            return static_cast<double>(lhs) < rhs.m_value || lhs == rhs;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator <= ( Approx const& lhs, T const& rhs ) {
+            return lhs.m_value < static_cast<double>(rhs) || lhs == rhs;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator >= ( T const& lhs, Approx const& rhs ) {
+            return static_cast<double>(lhs) > rhs.m_value || lhs == rhs;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator >= ( Approx const& lhs, T const& rhs ) {
+            return lhs.m_value > static_cast<double>(rhs) || lhs == rhs;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        Approx& epsilon( T const& newEpsilon ) {
+            double epsilonAsDouble = static_cast<double>(newEpsilon);
+            setEpsilon(epsilonAsDouble);
+            return *this;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        Approx& margin( T const& newMargin ) {
+            double marginAsDouble = static_cast<double>(newMargin);
+            setMargin(marginAsDouble);
+            return *this;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        Approx& scale( T const& newScale ) {
+            m_scale = static_cast<double>(newScale);
+            return *this;
+        }
+
+        std::string toString() const;
+
+    private:
+        double m_epsilon;
+        double m_margin;
+        double m_scale;
+        double m_value;
+    };
+} // end namespace Detail
+
+namespace literals {
+    Detail::Approx operator "" _a(long double val);
+    Detail::Approx operator "" _a(unsigned long long val);
+} // end namespace literals
+
+template<>
+struct StringMaker<Catch::Detail::Approx> {
+    static std::string convert(Catch::Detail::Approx const& value);
+};
+
+} // end namespace Catch
+
+// end catch_approx.h
+// start catch_string_manip.h
+
+#include <string>
+#include <iosfwd>
+
+namespace Catch {
+
+    bool startsWith( std::string const& s, std::string const& prefix );
+    bool startsWith( std::string const& s, char prefix );
+    bool endsWith( std::string const& s, std::string const& suffix );
+    bool endsWith( std::string const& s, char suffix );
+    bool contains( std::string const& s, std::string const& infix );
+    void toLowerInPlace( std::string& s );
+    std::string toLower( std::string const& s );
+    std::string trim( std::string const& str );
+    bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis );
+
+    struct pluralise {
+        pluralise( std::size_t count, std::string const& label );
+
+        friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser );
+
+        std::size_t m_count;
+        std::string m_label;
+    };
+}
+
+// end catch_string_manip.h
+#ifndef CATCH_CONFIG_DISABLE_MATCHERS
+// start catch_capture_matchers.h
+
+// start catch_matchers.h
+
+#include <string>
+#include <vector>
+
+namespace Catch {
+namespace Matchers {
+    namespace Impl {
+
+        template<typename ArgT> struct MatchAllOf;
+        template<typename ArgT> struct MatchAnyOf;
+        template<typename ArgT> struct MatchNotOf;
+
+        class MatcherUntypedBase {
+        public:
+            MatcherUntypedBase() = default;
+            MatcherUntypedBase ( MatcherUntypedBase const& ) = default;
+            MatcherUntypedBase& operator = ( MatcherUntypedBase const& ) = delete;
+            std::string toString() const;
+
+        protected:
+            virtual ~MatcherUntypedBase();
+            virtual std::string describe() const = 0;
+            mutable std::string m_cachedToString;
+        };
+
+#ifdef __clang__
+#    pragma clang diagnostic push
+#    pragma clang diagnostic ignored "-Wnon-virtual-dtor"
+#endif
+
+        template<typename ObjectT>
+        struct MatcherMethod {
+            virtual bool match( ObjectT const& arg ) const = 0;
+        };
+
+#ifdef __clang__
+#    pragma clang diagnostic pop
+#endif
+
+        template<typename T>
+        struct MatcherBase : MatcherUntypedBase, MatcherMethod<T> {
+
+            MatchAllOf<T> operator && ( MatcherBase const& other ) const;
+            MatchAnyOf<T> operator || ( MatcherBase const& other ) const;
+            MatchNotOf<T> operator ! () const;
+        };
+
+        template<typename ArgT>
+        struct MatchAllOf : MatcherBase<ArgT> {
+            bool match( ArgT const& arg ) const override {
+                for( auto matcher : m_matchers ) {
+                    if (!matcher->match(arg))
+                        return false;
+                }
+                return true;
+            }
+            std::string describe() const override {
+                std::string description;
+                description.reserve( 4 + m_matchers.size()*32 );
+                description += "( ";
+                bool first = true;
+                for( auto matcher : m_matchers ) {
+                    if( first )
+                        first = false;
+                    else
+                        description += " and ";
+                    description += matcher->toString();
+                }
+                description += " )";
+                return description;
+            }
+
+            MatchAllOf<ArgT>& operator && ( MatcherBase<ArgT> const& other ) {
+                m_matchers.push_back( &other );
+                return *this;
+            }
+
+            std::vector<MatcherBase<ArgT> const*> m_matchers;
+        };
+        template<typename ArgT>
+        struct MatchAnyOf : MatcherBase<ArgT> {
+
+            bool match( ArgT const& arg ) const override {
+                for( auto matcher : m_matchers ) {
+                    if (matcher->match(arg))
+                        return true;
+                }
+                return false;
+            }
+            std::string describe() const override {
+                std::string description;
+                description.reserve( 4 + m_matchers.size()*32 );
+                description += "( ";
+                bool first = true;
+                for( auto matcher : m_matchers ) {
+                    if( first )
+                        first = false;
+                    else
+                        description += " or ";
+                    description += matcher->toString();
+                }
+                description += " )";
+                return description;
+            }
+
+            MatchAnyOf<ArgT>& operator || ( MatcherBase<ArgT> const& other ) {
+                m_matchers.push_back( &other );
+                return *this;
+            }
+
+            std::vector<MatcherBase<ArgT> const*> m_matchers;
+        };
+
+        template<typename ArgT>
+        struct MatchNotOf : MatcherBase<ArgT> {
+
+            MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {}
+
+            bool match( ArgT const& arg ) const override {
+                return !m_underlyingMatcher.match( arg );
+            }
+
+            std::string describe() const override {
+                return "not " + m_underlyingMatcher.toString();
+            }
+            MatcherBase<ArgT> const& m_underlyingMatcher;
+        };
+
+        template<typename T>
+        MatchAllOf<T> MatcherBase<T>::operator && ( MatcherBase const& other ) const {
+            return MatchAllOf<T>() && *this && other;
+        }
+        template<typename T>
+        MatchAnyOf<T> MatcherBase<T>::operator || ( MatcherBase const& other ) const {
+            return MatchAnyOf<T>() || *this || other;
+        }
+        template<typename T>
+        MatchNotOf<T> MatcherBase<T>::operator ! () const {
+            return MatchNotOf<T>( *this );
+        }
+
+    } // namespace Impl
+
+} // namespace Matchers
+
+using namespace Matchers;
+using Matchers::Impl::MatcherBase;
+
+} // namespace Catch
+
+// end catch_matchers.h
+// start catch_matchers_floating.h
+
+#include <type_traits>
+#include <cmath>
+
+namespace Catch {
+namespace Matchers {
+
+    namespace Floating {
+
+        enum class FloatingPointKind : uint8_t;
+
+        struct WithinAbsMatcher : MatcherBase<double> {
+            WithinAbsMatcher(double target, double margin);
+            bool match(double const& matchee) const override;
+            std::string describe() const override;
+        private:
+            double m_target;
+            double m_margin;
+        };
+
+        struct WithinUlpsMatcher : MatcherBase<double> {
+            WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType);
+            bool match(double const& matchee) const override;
+            std::string describe() const override;
+        private:
+            double m_target;
+            int m_ulps;
+            FloatingPointKind m_type;
+        };
+
+    } // namespace Floating
+
+    // The following functions create the actual matcher objects.
+    // This allows the types to be inferred
+    Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff);
+    Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff);
+    Floating::WithinAbsMatcher WithinAbs(double target, double margin);
+
+} // namespace Matchers
+} // namespace Catch
+
+// end catch_matchers_floating.h
+// start catch_matchers_generic.hpp
+
+#include <functional>
+#include <string>
+
+namespace Catch {
+namespace Matchers {
+namespace Generic {
+
+namespace Detail {
+    std::string finalizeDescription(const std::string& desc);
+}
+
+template <typename T>
+class PredicateMatcher : public MatcherBase<T> {
+    std::function<bool(T const&)> m_predicate;
+    std::string m_description;
+public:
+
+    PredicateMatcher(std::function<bool(T const&)> const& elem, std::string const& descr)
+        :m_predicate(std::move(elem)),
+        m_description(Detail::finalizeDescription(descr))
+    {}
+
+    bool match( T const& item ) const override {
+        return m_predicate(item);
+    }
+
+    std::string describe() const override {
+        return m_description;
+    }
+};
+
+} // namespace Generic
+
+    // The following functions create the actual matcher objects.
+    // The user has to explicitly specify type to the function, because
+    // infering std::function<bool(T const&)> is hard (but possible) and
+    // requires a lot of TMP.
+    template<typename T>
+    Generic::PredicateMatcher<T> Predicate(std::function<bool(T const&)> const& predicate, std::string const& description = "") {
+        return Generic::PredicateMatcher<T>(predicate, description);
+    }
+
+} // namespace Matchers
+} // namespace Catch
+
+// end catch_matchers_generic.hpp
+// start catch_matchers_string.h
+
+#include <string>
+
+namespace Catch {
+namespace Matchers {
+
+    namespace StdString {
+
+        struct CasedString
+        {
+            CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity );
+            std::string adjustString( std::string const& str ) const;
+            std::string caseSensitivitySuffix() const;
+
+            CaseSensitive::Choice m_caseSensitivity;
+            std::string m_str;
+        };
+
+        struct StringMatcherBase : MatcherBase<std::string> {
+            StringMatcherBase( std::string const& operation, CasedString const& comparator );
+            std::string describe() const override;
+
+            CasedString m_comparator;
+            std::string m_operation;
+        };
+
+        struct EqualsMatcher : StringMatcherBase {
+            EqualsMatcher( CasedString const& comparator );
+            bool match( std::string const& source ) const override;
+        };
+        struct ContainsMatcher : StringMatcherBase {
+            ContainsMatcher( CasedString const& comparator );
+            bool match( std::string const& source ) const override;
+        };
+        struct StartsWithMatcher : StringMatcherBase {
+            StartsWithMatcher( CasedString const& comparator );
+            bool match( std::string const& source ) const override;
+        };
+        struct EndsWithMatcher : StringMatcherBase {
+            EndsWithMatcher( CasedString const& comparator );
+            bool match( std::string const& source ) const override;
+        };
+
+        struct RegexMatcher : MatcherBase<std::string> {
+            RegexMatcher( std::string regex, CaseSensitive::Choice caseSensitivity );
+            bool match( std::string const& matchee ) const override;
+            std::string describe() const override;
+
+        private:
+            std::string m_regex;
+            CaseSensitive::Choice m_caseSensitivity;
+        };
+
+    } // namespace StdString
+
+    // The following functions create the actual matcher objects.
+    // This allows the types to be inferred
+
+    StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
+    StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
+    StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
+    StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
+    StdString::RegexMatcher Matches( std::string const& regex, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
+
+} // namespace Matchers
+} // namespace Catch
+
+// end catch_matchers_string.h
+// start catch_matchers_vector.h
+
+#include <algorithm>
+
+namespace Catch {
+namespace Matchers {
+
+    namespace Vector {
+        namespace Detail {
+            template <typename InputIterator, typename T>
+            size_t count(InputIterator first, InputIterator last, T const& item) {
+                size_t cnt = 0;
+                for (; first != last; ++first) {
+                    if (*first == item) {
+                        ++cnt;
+                    }
+                }
+                return cnt;
+            }
+            template <typename InputIterator, typename T>
+            bool contains(InputIterator first, InputIterator last, T const& item) {
+                for (; first != last; ++first) {
+                    if (*first == item) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        }
+
+        template<typename T>
+        struct ContainsElementMatcher : MatcherBase<std::vector<T>> {
+
+            ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {}
+
+            bool match(std::vector<T> const &v) const override {
+                for (auto const& el : v) {
+                    if (el == m_comparator) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+
+            std::string describe() const override {
+                return "Contains: " + ::Catch::Detail::stringify( m_comparator );
+            }
+
+            T const& m_comparator;
+        };
+
+        template<typename T>
+        struct ContainsMatcher : MatcherBase<std::vector<T>> {
+
+            ContainsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {}
+
+            bool match(std::vector<T> const &v) const override {
+                // !TBD: see note in EqualsMatcher
+                if (m_comparator.size() > v.size())
+                    return false;
+                for (auto const& comparator : m_comparator) {
+                    auto present = false;
+                    for (const auto& el : v) {
+                        if (el == comparator) {
+                            present = true;
+                            break;
+                        }
+                    }
+                    if (!present) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+            std::string describe() const override {
+                return "Contains: " + ::Catch::Detail::stringify( m_comparator );
+            }
+
+            std::vector<T> const& m_comparator;
+        };
+
+        template<typename T>
+        struct EqualsMatcher : MatcherBase<std::vector<T>> {
+
+            EqualsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {}
+
+            bool match(std::vector<T> const &v) const override {
+                // !TBD: This currently works if all elements can be compared using !=
+                // - a more general approach would be via a compare template that defaults
+                // to using !=. but could be specialised for, e.g. std::vector<T> etc
+                // - then just call that directly
+                if (m_comparator.size() != v.size())
+                    return false;
+                for (std::size_t i = 0; i < v.size(); ++i)
+                    if (m_comparator[i] != v[i])
+                        return false;
+                return true;
+            }
+            std::string describe() const override {
+                return "Equals: " + ::Catch::Detail::stringify( m_comparator );
+            }
+            std::vector<T> const& m_comparator;
+        };
+
+        template<typename T>
+        struct UnorderedEqualsMatcher : MatcherBase<std::vector<T>> {
+            UnorderedEqualsMatcher(std::vector<T> const& target) : m_target(target) {}
+            bool match(std::vector<T> const& vec) const override {
+                // Note: This is a reimplementation of std::is_permutation,
+                //       because I don't want to include <algorithm> inside the common path
+                if (m_target.size() != vec.size()) {
+                    return false;
+                }
+                auto lfirst = m_target.begin(), llast = m_target.end();
+                auto rfirst = vec.begin(), rlast = vec.end();
+                // Cut common prefix to optimize checking of permuted parts
+                while (lfirst != llast && *lfirst == *rfirst) {
+                    ++lfirst; ++rfirst;
+                }
+                if (lfirst == llast) {
+                    return true;
+                }
+
+                for (auto mid = lfirst; mid != llast; ++mid) {
+                    // Skip already counted items
+                    if (Detail::contains(lfirst, mid, *mid)) {
+                        continue;
+                    }
+                    size_t num_vec = Detail::count(rfirst, rlast, *mid);
+                    if (num_vec == 0 || Detail::count(lfirst, llast, *mid) != num_vec) {
+                        return false;
+                    }
+                }
+
+                return true;
+            }
+
+            std::string describe() const override {
+                return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target);
+            }
+        private:
+            std::vector<T> const& m_target;
+        };
+
+    } // namespace Vector
+
+    // The following functions create the actual matcher objects.
+    // This allows the types to be inferred
+
+    template<typename T>
+    Vector::ContainsMatcher<T> Contains( std::vector<T> const& comparator ) {
+        return Vector::ContainsMatcher<T>( comparator );
+    }
+
+    template<typename T>
+    Vector::ContainsElementMatcher<T> VectorContains( T const& comparator ) {
+        return Vector::ContainsElementMatcher<T>( comparator );
+    }
+
+    template<typename T>
+    Vector::EqualsMatcher<T> Equals( std::vector<T> const& comparator ) {
+        return Vector::EqualsMatcher<T>( comparator );
+    }
+
+    template<typename T>
+    Vector::UnorderedEqualsMatcher<T> UnorderedEquals(std::vector<T> const& target) {
+        return Vector::UnorderedEqualsMatcher<T>(target);
+    }
+
+} // namespace Matchers
+} // namespace Catch
+
+// end catch_matchers_vector.h
+namespace Catch {
+
+    template<typename ArgT, typename MatcherT>
+    class MatchExpr : public ITransientExpression {
+        ArgT const& m_arg;
+        MatcherT m_matcher;
+        StringRef m_matcherString;
+    public:
+        MatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString )
+        :   ITransientExpression{ true, matcher.match( arg ) },
+            m_arg( arg ),
+            m_matcher( matcher ),
+            m_matcherString( matcherString )
+        {}
+
+        void streamReconstructedExpression( std::ostream &os ) const override {
+            auto matcherAsString = m_matcher.toString();
+            os << Catch::Detail::stringify( m_arg ) << ' ';
+            if( matcherAsString == Detail::unprintableString )
+                os << m_matcherString;
+            else
+                os << matcherAsString;
+        }
+    };
+
+    using StringMatcher = Matchers::Impl::MatcherBase<std::string>;
+
+    void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString  );
+
+    template<typename ArgT, typename MatcherT>
+    auto makeMatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString  ) -> MatchExpr<ArgT, MatcherT> {
+        return MatchExpr<ArgT, MatcherT>( arg, matcher, matcherString );
+    }
+
+} // namespace Catch
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \
+    do { \
+        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \
+        INTERNAL_CATCH_TRY { \
+            catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher, #matcher##_catch_sr ) ); \
+        } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \
+        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
+    } while( false )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \
+    do { \
+        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \
+        if( catchAssertionHandler.allowThrows() ) \
+            try { \
+                static_cast<void>(__VA_ARGS__ ); \
+                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \
+            } \
+            catch( exceptionType const& ex ) { \
+                catchAssertionHandler.handleExpr( Catch::makeMatchExpr( ex, matcher, #matcher##_catch_sr ) ); \
+            } \
+            catch( ... ) { \
+                catchAssertionHandler.handleUnexpectedInflightException(); \
+            } \
+        else \
+            catchAssertionHandler.handleThrowingCallSkipped(); \
+        INTERNAL_CATCH_REACT( catchAssertionHandler ) \
+    } while( false )
+
+// end catch_capture_matchers.h
+#endif
+// start catch_generators.hpp
+
+// start catch_interfaces_generatortracker.h
+
+
+#include <memory>
+
+namespace Catch {
+
+    namespace Generators {
+        class GeneratorBase {
+        protected:
+            size_t m_size = 0;
+
+        public:
+            GeneratorBase( size_t size ) : m_size( size ) {}
+            virtual ~GeneratorBase();
+            auto size() const -> size_t { return m_size; }
+        };
+        using GeneratorBasePtr = std::unique_ptr<GeneratorBase>;
+
+    } // namespace Generators
+
+    struct IGeneratorTracker {
+        virtual ~IGeneratorTracker();
+        virtual auto hasGenerator() const -> bool = 0;
+        virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0;
+        virtual void setGenerator( Generators::GeneratorBasePtr&& generator ) = 0;
+        virtual auto getIndex() const -> std::size_t = 0;
+    };
+
+} // namespace Catch
+
+// end catch_interfaces_generatortracker.h
+// start catch_enforce.h
+
+#include <stdexcept>
+
+namespace Catch {
+#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
+    template <typename Ex>
+    [[noreturn]]
+    void throw_exception(Ex const& e) {
+        throw e;
+    }
+#else // ^^ Exceptions are enabled //  Exceptions are disabled vv
+    [[noreturn]]
+    void throw_exception(std::exception const& e);
+#endif
+} // namespace Catch;
+
+#define CATCH_PREPARE_EXCEPTION( type, msg ) \
+    type( ( Catch::ReusableStringStream() << msg ).str() )
+#define CATCH_INTERNAL_ERROR( msg ) \
+    Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg))
+#define CATCH_ERROR( msg ) \
+    Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::domain_error, msg ))
+#define CATCH_RUNTIME_ERROR( msg ) \
+    Catch::throw_exception(CATCH_PREPARE_EXCEPTION( std::runtime_error, msg ))
+#define CATCH_ENFORCE( condition, msg ) \
+    do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false)
+
+// end catch_enforce.h
+#include <memory>
+#include <vector>
+#include <cassert>
+
+#include <utility>
+
+namespace Catch {
+namespace Generators {
+
+    // !TBD move this into its own location?
+    namespace pf{
+        template<typename T, typename... Args>
+        std::unique_ptr<T> make_unique( Args&&... args ) {
+            return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+        }
+    }
+
+    template<typename T>
+    struct IGenerator {
+        virtual ~IGenerator() {}
+        virtual auto get( size_t index ) const -> T = 0;
+    };
+
+    template<typename T>
+    class SingleValueGenerator : public IGenerator<T> {
+        T m_value;
+    public:
+        SingleValueGenerator( T const& value ) : m_value( value ) {}
+
+        auto get( size_t ) const -> T override {
+            return m_value;
+        }
+    };
+
+    template<typename T>
+    class FixedValuesGenerator : public IGenerator<T> {
+        std::vector<T> m_values;
+
+    public:
+        FixedValuesGenerator( std::initializer_list<T> values ) : m_values( values ) {}
+
+        auto get( size_t index ) const -> T override {
+            return m_values[index];
+        }
+    };
+
+    template<typename T>
+    class RangeGenerator : public IGenerator<T> {
+        T const m_first;
+        T const m_last;
+
+    public:
+        RangeGenerator( T const& first, T const& last ) : m_first( first ), m_last( last ) {
+            assert( m_last > m_first );
+        }
+
+        auto get( size_t index ) const -> T override {
+            // ToDo:: introduce a safe cast to catch potential overflows
+            return static_cast<T>(m_first+index);
+        }
+    };
+
+    template<typename T>
+    struct NullGenerator : IGenerator<T> {
+        auto get( size_t ) const -> T override {
+            CATCH_INTERNAL_ERROR("A Null Generator is always empty");
+        }
+    };
+
+    template<typename T>
+    class Generator {
+        std::unique_ptr<IGenerator<T>> m_generator;
+        size_t m_size;
+
+    public:
+        Generator( size_t size, std::unique_ptr<IGenerator<T>> generator )
+        :   m_generator( std::move( generator ) ),
+            m_size( size )
+        {}
+
+        auto size() const -> size_t { return m_size; }
+        auto operator[]( size_t index ) const -> T {
+            assert( index < m_size );
+            return m_generator->get( index );
+        }
+    };
+
+    std::vector<size_t> randomiseIndices( size_t selectionSize, size_t sourceSize );
+
+    template<typename T>
+    class GeneratorRandomiser : public IGenerator<T> {
+        Generator<T> m_baseGenerator;
+
+        std::vector<size_t> m_indices;
+    public:
+        GeneratorRandomiser( Generator<T>&& baseGenerator, size_t numberOfItems )
+        :   m_baseGenerator( std::move( baseGenerator ) ),
+            m_indices( randomiseIndices( numberOfItems, m_baseGenerator.size() ) )
+        {}
+
+        auto get( size_t index ) const -> T override {
+            return m_baseGenerator[m_indices[index]];
+        }
+    };
+
+    template<typename T>
+    struct RequiresASpecialisationFor;
+
+    template<typename T>
+    auto all() -> Generator<T> { return RequiresASpecialisationFor<T>(); }
+
+    template<>
+    auto all<int>() -> Generator<int>;
+
+    template<typename T>
+    auto range( T const& first, T const& last ) -> Generator<T> {
+        return Generator<T>( (last-first), pf::make_unique<RangeGenerator<T>>( first, last ) );
+    }
+
+    template<typename T>
+    auto random( T const& first, T const& last ) -> Generator<T> {
+        auto gen = range( first, last );
+        auto size = gen.size();
+
+        return Generator<T>( size, pf::make_unique<GeneratorRandomiser<T>>( std::move( gen ), size ) );
+    }
+    template<typename T>
+    auto random( size_t size ) -> Generator<T> {
+        return Generator<T>( size, pf::make_unique<GeneratorRandomiser<T>>( all<T>(), size ) );
+    }
+
+    template<typename T>
+    auto values( std::initializer_list<T> values ) -> Generator<T> {
+        return Generator<T>( values.size(), pf::make_unique<FixedValuesGenerator<T>>( values ) );
+    }
+    template<typename T>
+    auto value( T const& val ) -> Generator<T> {
+        return Generator<T>( 1, pf::make_unique<SingleValueGenerator<T>>( val ) );
+    }
+
+    template<typename T>
+    auto as() -> Generator<T> {
+        return Generator<T>( 0, pf::make_unique<NullGenerator<T>>() );
+    }
+
+    template<typename... Ts>
+    auto table( std::initializer_list<std::tuple<Ts...>>&& tuples ) -> Generator<std::tuple<Ts...>> {
+        return values<std::tuple<Ts...>>( std::forward<std::initializer_list<std::tuple<Ts...>>>( tuples ) );
+    }
+
+    template<typename T>
+    struct Generators : GeneratorBase {
+        std::vector<Generator<T>> m_generators;
+
+        using type = T;
+
+        Generators() : GeneratorBase( 0 ) {}
+
+        void populate( T&& val ) {
+            m_size += 1;
+            m_generators.emplace_back( value( std::move( val ) ) );
+        }
+        template<typename U>
+        void populate( U&& val ) {
+            populate( T( std::move( val ) ) );
+        }
+        void populate( Generator<T>&& generator ) {
+            m_size += generator.size();
+            m_generators.emplace_back( std::move( generator ) );
+        }
+
+        template<typename U, typename... Gs>
+        void populate( U&& valueOrGenerator, Gs... moreGenerators ) {
+            populate( std::forward<U>( valueOrGenerator ) );
+            populate( std::forward<Gs>( moreGenerators )... );
+        }
+
+        auto operator[]( size_t index ) const -> T {
+            size_t sizes = 0;
+            for( auto const& gen : m_generators ) {
+                auto localIndex = index-sizes;
+                sizes += gen.size();
+                if( index < sizes )
+                    return gen[localIndex];
+            }
+            CATCH_INTERNAL_ERROR("Index '" << index << "' is out of range (" << sizes << ')');
+        }
+    };
+
+    template<typename T, typename... Gs>
+    auto makeGenerators( Generator<T>&& generator, Gs... moreGenerators ) -> Generators<T> {
+        Generators<T> generators;
+        generators.m_generators.reserve( 1+sizeof...(Gs) );
+        generators.populate( std::move( generator ), std::forward<Gs>( moreGenerators )... );
+        return generators;
+    }
+    template<typename T>
+    auto makeGenerators( Generator<T>&& generator ) -> Generators<T> {
+        Generators<T> generators;
+        generators.populate( std::move( generator ) );
+        return generators;
+    }
+    template<typename T, typename... Gs>
+    auto makeGenerators( T&& val, Gs... moreGenerators ) -> Generators<T> {
+        return makeGenerators( value( std::forward<T>( val ) ), std::forward<Gs>( moreGenerators )... );
+    }
+    template<typename T, typename U, typename... Gs>
+    auto makeGenerators( U&& val, Gs... moreGenerators ) -> Generators<T> {
+        return makeGenerators( value( T( std::forward<U>( val ) ) ), std::forward<Gs>( moreGenerators )... );
+    }
+
+    auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker&;
+
+    template<typename L>
+    // Note: The type after -> is weird, because VS2015 cannot parse
+    //       the expression used in the typedef inside, when it is in
+    //       return type. Yeah, ¯\_(ツ)_/¯
+    auto generate( SourceLineInfo const& lineInfo, L const& generatorExpression ) -> decltype(std::declval<decltype(generatorExpression())>()[0]) {
+        using UnderlyingType = typename decltype(generatorExpression())::type;
+
+        IGeneratorTracker& tracker = acquireGeneratorTracker( lineInfo );
+        if( !tracker.hasGenerator() )
+            tracker.setGenerator( pf::make_unique<Generators<UnderlyingType>>( generatorExpression() ) );
+
+        auto const& generator = static_cast<Generators<UnderlyingType> const&>( *tracker.getGenerator() );
+        return generator[tracker.getIndex()];
+    }
+
+} // namespace Generators
+} // namespace Catch
+
+#define GENERATE( ... ) \
+    Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, []{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } )
+
+// end catch_generators.hpp
+
+// These files are included here so the single_include script doesn't put them
+// in the conditionally compiled sections
+// start catch_test_case_info.h
+
+#include <string>
+#include <vector>
+#include <memory>
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+namespace Catch {
+
+    struct ITestInvoker;
+
+    struct TestCaseInfo {
+        enum SpecialProperties{
+            None = 0,
+            IsHidden = 1 << 1,
+            ShouldFail = 1 << 2,
+            MayFail = 1 << 3,
+            Throws = 1 << 4,
+            NonPortable = 1 << 5,
+            Benchmark = 1 << 6
+        };
+
+        TestCaseInfo(   std::string const& _name,
+                        std::string const& _className,
+                        std::string const& _description,
+                        std::vector<std::string> const& _tags,
+                        SourceLineInfo const& _lineInfo );
+
+        friend void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags );
+
+        bool isHidden() const;
+        bool throws() const;
+        bool okToFail() const;
+        bool expectedToFail() const;
+
+        std::string tagsAsString() const;
+
+        std::string name;
+        std::string className;
+        std::string description;
+        std::vector<std::string> tags;
+        std::vector<std::string> lcaseTags;
+        SourceLineInfo lineInfo;
+        SpecialProperties properties;
+    };
+
+    class TestCase : public TestCaseInfo {
+    public:
+
+        TestCase( ITestInvoker* testCase, TestCaseInfo&& info );
+
+        TestCase withName( std::string const& _newName ) const;
+
+        void invoke() const;
+
+        TestCaseInfo const& getTestCaseInfo() const;
+
+        bool operator == ( TestCase const& other ) const;
+        bool operator < ( TestCase const& other ) const;
+
+    private:
+        std::shared_ptr<ITestInvoker> test;
+    };
+
+    TestCase makeTestCase(  ITestInvoker* testCase,
+                            std::string const& className,
+                            NameAndTags const& nameAndTags,
+                            SourceLineInfo const& lineInfo );
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+// end catch_test_case_info.h
+// start catch_interfaces_runner.h
+
+namespace Catch {
+
+    struct IRunner {
+        virtual ~IRunner();
+        virtual bool aborting() const = 0;
+    };
+}
+
+// end catch_interfaces_runner.h
+
+#ifdef __OBJC__
+// start catch_objc.hpp
+
+#import <objc/runtime.h>
+
+#include <string>
+
+// NB. Any general catch headers included here must be included
+// in catch.hpp first to make sure they are included by the single
+// header for non obj-usage
+
+///////////////////////////////////////////////////////////////////////////////
+// This protocol is really only here for (self) documenting purposes, since
+// all its methods are optional.
+@protocol OcFixture
+
+@optional
+
+-(void) setUp;
+-(void) tearDown;
+
+@end
+
+namespace Catch {
+
+    class OcMethod : public ITestInvoker {
+
+    public:
+        OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {}
+
+        virtual void invoke() const {
+            id obj = [[m_cls alloc] init];
+
+            performOptionalSelector( obj, @selector(setUp)  );
+            performOptionalSelector( obj, m_sel );
+            performOptionalSelector( obj, @selector(tearDown)  );
+
+            arcSafeRelease( obj );
+        }
+    private:
+        virtual ~OcMethod() {}
+
+        Class m_cls;
+        SEL m_sel;
+    };
+
+    namespace Detail{
+
+        inline std::string getAnnotation(   Class cls,
+                                            std::string const& annotationName,
+                                            std::string const& testCaseName ) {
+            NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()];
+            SEL sel = NSSelectorFromString( selStr );
+            arcSafeRelease( selStr );
+            id value = performOptionalSelector( cls, sel );
+            if( value )
+                return [(NSString*)value UTF8String];
+            return "";
+        }
+    }
+
+    inline std::size_t registerTestMethods() {
+        std::size_t noTestMethods = 0;
+        int noClasses = objc_getClassList( nullptr, 0 );
+
+        Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses);
+        objc_getClassList( classes, noClasses );
+
+        for( int c = 0; c < noClasses; c++ ) {
+            Class cls = classes[c];
+            {
+                u_int count;
+                Method* methods = class_copyMethodList( cls, &count );
+                for( u_int m = 0; m < count ; m++ ) {
+                    SEL selector = method_getName(methods[m]);
+                    std::string methodName = sel_getName(selector);
+                    if( startsWith( methodName, "Catch_TestCase_" ) ) {
+                        std::string testCaseName = methodName.substr( 15 );
+                        std::string name = Detail::getAnnotation( cls, "Name", testCaseName );
+                        std::string desc = Detail::getAnnotation( cls, "Description", testCaseName );
+                        const char* className = class_getName( cls );
+
+                        getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, NameAndTags( name.c_str(), desc.c_str() ), SourceLineInfo("",0) ) );
+                        noTestMethods++;
+                    }
+                }
+                free(methods);
+            }
+        }
+        return noTestMethods;
+    }
+
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+
+    namespace Matchers {
+        namespace Impl {
+        namespace NSStringMatchers {
+
+            struct StringHolder : MatcherBase<NSString*>{
+                StringHolder( NSString* substr ) : m_substr( [substr copy] ){}
+                StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){}
+                StringHolder() {
+                    arcSafeRelease( m_substr );
+                }
+
+                bool match( NSString* arg ) const override {
+                    return false;
+                }
+
+                NSString* CATCH_ARC_STRONG m_substr;
+            };
+
+            struct Equals : StringHolder {
+                Equals( NSString* substr ) : StringHolder( substr ){}
+
+                bool match( NSString* str ) const override {
+                    return  (str != nil || m_substr == nil ) &&
+                            [str isEqualToString:m_substr];
+                }
+
+                std::string describe() const override {
+                    return "equals string: " + Catch::Detail::stringify( m_substr );
+                }
+            };
+
+            struct Contains : StringHolder {
+                Contains( NSString* substr ) : StringHolder( substr ){}
+
+                bool match( NSString* str ) const {
+                    return  (str != nil || m_substr == nil ) &&
+                            [str rangeOfString:m_substr].location != NSNotFound;
+                }
+
+                std::string describe() const override {
+                    return "contains string: " + Catch::Detail::stringify( m_substr );
+                }
+            };
+
+            struct StartsWith : StringHolder {
+                StartsWith( NSString* substr ) : StringHolder( substr ){}
+
+                bool match( NSString* str ) const override {
+                    return  (str != nil || m_substr == nil ) &&
+                            [str rangeOfString:m_substr].location == 0;
+                }
+
+                std::string describe() const override {
+                    return "starts with: " + Catch::Detail::stringify( m_substr );
+                }
+            };
+            struct EndsWith : StringHolder {
+                EndsWith( NSString* substr ) : StringHolder( substr ){}
+
+                bool match( NSString* str ) const override {
+                    return  (str != nil || m_substr == nil ) &&
+                            [str rangeOfString:m_substr].location == [str length] - [m_substr length];
+                }
+
+                std::string describe() const override {
+                    return "ends with: " + Catch::Detail::stringify( m_substr );
+                }
+            };
+
+        } // namespace NSStringMatchers
+        } // namespace Impl
+
+        inline Impl::NSStringMatchers::Equals
+            Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); }
+
+        inline Impl::NSStringMatchers::Contains
+            Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); }
+
+        inline Impl::NSStringMatchers::StartsWith
+            StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); }
+
+        inline Impl::NSStringMatchers::EndsWith
+            EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); }
+
+    } // namespace Matchers
+
+    using namespace Matchers;
+
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+
+} // namespace Catch
+
+///////////////////////////////////////////////////////////////////////////////
+#define OC_MAKE_UNIQUE_NAME( root, uniqueSuffix ) root##uniqueSuffix
+#define OC_TEST_CASE2( name, desc, uniqueSuffix ) \
++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Name_test_, uniqueSuffix ) \
+{ \
+return @ name; \
+} \
++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Description_test_, uniqueSuffix ) \
+{ \
+return @ desc; \
+} \
+-(void) OC_MAKE_UNIQUE_NAME( Catch_TestCase_test_, uniqueSuffix )
+
+#define OC_TEST_CASE( name, desc ) OC_TEST_CASE2( name, desc, __LINE__ )
+
+// end catch_objc.hpp
+#endif
+
+#ifdef CATCH_CONFIG_EXTERNAL_INTERFACES
+// start catch_external_interfaces.h
+
+// start catch_reporter_bases.hpp
+
+// start catch_interfaces_reporter.h
+
+// start catch_config.hpp
+
+// start catch_test_spec_parser.h
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+// start catch_test_spec.h
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+// start catch_wildcard_pattern.h
+
+namespace Catch
+{
+    class WildcardPattern {
+        enum WildcardPosition {
+            NoWildcard = 0,
+            WildcardAtStart = 1,
+            WildcardAtEnd = 2,
+            WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd
+        };
+
+    public:
+
+        WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity );
+        virtual ~WildcardPattern() = default;
+        virtual bool matches( std::string const& str ) const;
+
+    private:
+        std::string adjustCase( std::string const& str ) const;
+        CaseSensitive::Choice m_caseSensitivity;
+        WildcardPosition m_wildcard = NoWildcard;
+        std::string m_pattern;
+    };
+}
+
+// end catch_wildcard_pattern.h
+#include <string>
+#include <vector>
+#include <memory>
+
+namespace Catch {
+
+    class TestSpec {
+        struct Pattern {
+            virtual ~Pattern();
+            virtual bool matches( TestCaseInfo const& testCase ) const = 0;
+        };
+        using PatternPtr = std::shared_ptr<Pattern>;
+
+        class NamePattern : public Pattern {
+        public:
+            NamePattern( std::string const& name );
+            virtual ~NamePattern();
+            virtual bool matches( TestCaseInfo const& testCase ) const override;
+        private:
+            WildcardPattern m_wildcardPattern;
+        };
+
+        class TagPattern : public Pattern {
+        public:
+            TagPattern( std::string const& tag );
+            virtual ~TagPattern();
+            virtual bool matches( TestCaseInfo const& testCase ) const override;
+        private:
+            std::string m_tag;
+        };
+
+        class ExcludedPattern : public Pattern {
+        public:
+            ExcludedPattern( PatternPtr const& underlyingPattern );
+            virtual ~ExcludedPattern();
+            virtual bool matches( TestCaseInfo const& testCase ) const override;
+        private:
+            PatternPtr m_underlyingPattern;
+        };
+
+        struct Filter {
+            std::vector<PatternPtr> m_patterns;
+
+            bool matches( TestCaseInfo const& testCase ) const;
+        };
+
+    public:
+        bool hasFilters() const;
+        bool matches( TestCaseInfo const& testCase ) const;
+
+    private:
+        std::vector<Filter> m_filters;
+
+        friend class TestSpecParser;
+    };
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+// end catch_test_spec.h
+// start catch_interfaces_tag_alias_registry.h
+
+#include <string>
+
+namespace Catch {
+
+    struct TagAlias;
+
+    struct ITagAliasRegistry {
+        virtual ~ITagAliasRegistry();
+        // Nullptr if not present
+        virtual TagAlias const* find( std::string const& alias ) const = 0;
+        virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0;
+
+        static ITagAliasRegistry const& get();
+    };
+
+} // end namespace Catch
+
+// end catch_interfaces_tag_alias_registry.h
+namespace Catch {
+
+    class TestSpecParser {
+        enum Mode{ None, Name, QuotedName, Tag, EscapedName };
+        Mode m_mode = None;
+        bool m_exclusion = false;
+        std::size_t m_start = std::string::npos, m_pos = 0;
+        std::string m_arg;
+        std::vector<std::size_t> m_escapeChars;
+        TestSpec::Filter m_currentFilter;
+        TestSpec m_testSpec;
+        ITagAliasRegistry const* m_tagAliases = nullptr;
+
+    public:
+        TestSpecParser( ITagAliasRegistry const& tagAliases );
+
+        TestSpecParser& parse( std::string const& arg );
+        TestSpec testSpec();
+
+    private:
+        void visitChar( char c );
+        void startNewMode( Mode mode, std::size_t start );
+        void escape();
+        std::string subString() const;
+
+        template<typename T>
+        void addPattern() {
+            std::string token = subString();
+            for( std::size_t i = 0; i < m_escapeChars.size(); ++i )
+                token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 );
+            m_escapeChars.clear();
+            if( startsWith( token, "exclude:" ) ) {
+                m_exclusion = true;
+                token = token.substr( 8 );
+            }
+            if( !token.empty() ) {
+                TestSpec::PatternPtr pattern = std::make_shared<T>( token );
+                if( m_exclusion )
+                    pattern = std::make_shared<TestSpec::ExcludedPattern>( pattern );
+                m_currentFilter.m_patterns.push_back( pattern );
+            }
+            m_exclusion = false;
+            m_mode = None;
+        }
+
+        void addFilter();
+    };
+    TestSpec parseTestSpec( std::string const& arg );
+
+} // namespace Catch
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+// end catch_test_spec_parser.h
+// start catch_interfaces_config.h
+
+#include <iosfwd>
+#include <string>
+#include <vector>
+#include <memory>
+
+namespace Catch {
+
+    enum class Verbosity {
+        Quiet = 0,
+        Normal,
+        High
+    };
+
+    struct WarnAbout { enum What {
+        Nothing = 0x00,
+        NoAssertions = 0x01,
+        NoTests = 0x02
+    }; };
+
+    struct ShowDurations { enum OrNot {
+        DefaultForReporter,
+        Always,
+        Never
+    }; };
+    struct RunTests { enum InWhatOrder {
+        InDeclarationOrder,
+        InLexicographicalOrder,
+        InRandomOrder
+    }; };
+    struct UseColour { enum YesOrNo {
+        Auto,
+        Yes,
+        No
+    }; };
+    struct WaitForKeypress { enum When {
+        Never,
+        BeforeStart = 1,
+        BeforeExit = 2,
+        BeforeStartAndExit = BeforeStart | BeforeExit
+    }; };
+
+    class TestSpec;
+
+    struct IConfig : NonCopyable {
+
+        virtual ~IConfig();
+
+        virtual bool allowThrows() const = 0;
+        virtual std::ostream& stream() const = 0;
+        virtual std::string name() const = 0;
+        virtual bool includeSuccessfulResults() const = 0;
+        virtual bool shouldDebugBreak() const = 0;
+        virtual bool warnAboutMissingAssertions() const = 0;
+        virtual bool warnAboutNoTests() const = 0;
+        virtual int abortAfter() const = 0;
+        virtual bool showInvisibles() const = 0;
+        virtual ShowDurations::OrNot showDurations() const = 0;
+        virtual TestSpec const& testSpec() const = 0;
+        virtual bool hasTestFilters() const = 0;
+        virtual RunTests::InWhatOrder runOrder() const = 0;
+        virtual unsigned int rngSeed() const = 0;
+        virtual int benchmarkResolutionMultiple() const = 0;
+        virtual UseColour::YesOrNo useColour() const = 0;
+        virtual std::vector<std::string> const& getSectionsToRun() const = 0;
+        virtual Verbosity verbosity() const = 0;
+    };
+
+    using IConfigPtr = std::shared_ptr<IConfig const>;
+}
+
+// end catch_interfaces_config.h
+// Libstdc++ doesn't like incomplete classes for unique_ptr
+
+#include <memory>
+#include <vector>
+#include <string>
+
+#ifndef CATCH_CONFIG_CONSOLE_WIDTH
+#define CATCH_CONFIG_CONSOLE_WIDTH 80
+#endif
+
+namespace Catch {
+
+    struct IStream;
+
+    struct ConfigData {
+        bool listTests = false;
+        bool listTags = false;
+        bool listReporters = false;
+        bool listTestNamesOnly = false;
+
+        bool showSuccessfulTests = false;
+        bool shouldDebugBreak = false;
+        bool noThrow = false;
+        bool showHelp = false;
+        bool showInvisibles = false;
+        bool filenamesAsTags = false;
+        bool libIdentify = false;
+
+        int abortAfter = -1;
+        unsigned int rngSeed = 0;
+        int benchmarkResolutionMultiple = 100;
+
+        Verbosity verbosity = Verbosity::Normal;
+        WarnAbout::What warnings = WarnAbout::Nothing;
+        ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter;
+        RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder;
+        UseColour::YesOrNo useColour = UseColour::Auto;
+        WaitForKeypress::When waitForKeypress = WaitForKeypress::Never;
+
+        std::string outputFilename;
+        std::string name;
+        std::string processName;
+#ifndef CATCH_CONFIG_DEFAULT_REPORTER
+#define CATCH_CONFIG_DEFAULT_REPORTER "console"
+#endif
+        std::string reporterName = CATCH_CONFIG_DEFAULT_REPORTER;
+#undef CATCH_CONFIG_DEFAULT_REPORTER
+
+        std::vector<std::string> testsOrTags;
+        std::vector<std::string> sectionsToRun;
+    };
+
+    class Config : public IConfig {
+    public:
+
+        Config() = default;
+        Config( ConfigData const& data );
+        virtual ~Config() = default;
+
+        std::string const& getFilename() const;
+
+        bool listTests() const;
+        bool listTestNamesOnly() const;
+        bool listTags() const;
+        bool listReporters() const;
+
+        std::string getProcessName() const;
+        std::string const& getReporterName() const;
+
+        std::vector<std::string> const& getTestsOrTags() const;
+        std::vector<std::string> const& getSectionsToRun() const override;
+
+        virtual TestSpec const& testSpec() const override;
+        bool hasTestFilters() const override;
+
+        bool showHelp() const;
+
+        // IConfig interface
+        bool allowThrows() const override;
+        std::ostream& stream() const override;
+        std::string name() const override;
+        bool includeSuccessfulResults() const override;
+        bool warnAboutMissingAssertions() const override;
+        bool warnAboutNoTests() const override;
+        ShowDurations::OrNot showDurations() const override;
+        RunTests::InWhatOrder runOrder() const override;
+        unsigned int rngSeed() const override;
+        int benchmarkResolutionMultiple() const override;
+        UseColour::YesOrNo useColour() const override;
+        bool shouldDebugBreak() const override;
+        int abortAfter() const override;
+        bool showInvisibles() const override;
+        Verbosity verbosity() const override;
+
+    private:
+
+        IStream const* openStream();
+        ConfigData m_data;
+
+        std::unique_ptr<IStream const> m_stream;
+        TestSpec m_testSpec;
+        bool m_hasTestFilters = false;
+    };
+
+} // end namespace Catch
+
+// end catch_config.hpp
+// start catch_assertionresult.h
+
+#include <string>
+
+namespace Catch {
+
+    struct AssertionResultData
+    {
+        AssertionResultData() = delete;
+
+        AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression );
+
+        std::string message;
+        mutable std::string reconstructedExpression;
+        LazyExpression lazyExpression;
+        ResultWas::OfType resultType;
+
+        std::string reconstructExpression() const;
+    };
+
+    class AssertionResult {
+    public:
+        AssertionResult() = delete;
+        AssertionResult( AssertionInfo const& info, AssertionResultData const& data );
+
+        bool isOk() const;
+        bool succeeded() const;
+        ResultWas::OfType getResultType() const;
+        bool hasExpression() const;
+        bool hasMessage() const;
+        std::string getExpression() const;
+        std::string getExpressionInMacro() const;
+        bool hasExpandedExpression() const;
+        std::string getExpandedExpression() const;
+        std::string getMessage() const;
+        SourceLineInfo getSourceInfo() const;
+        StringRef getTestMacroName() const;
+
+    //protected:
+        AssertionInfo m_info;
+        AssertionResultData m_resultData;
+    };
+
+} // end namespace Catch
+
+// end catch_assertionresult.h
+// start catch_option.hpp
+
+namespace Catch {
+
+    // An optional type
+    template<typename T>
+    class Option {
+    public:
+        Option() : nullableValue( nullptr ) {}
+        Option( T const& _value )
+        : nullableValue( new( storage ) T( _value ) )
+        {}
+        Option( Option const& _other )
+        : nullableValue( _other ? new( storage ) T( *_other ) : nullptr )
+        {}
+
+        ~Option() {
+            reset();
+        }
+
+        Option& operator= ( Option const& _other ) {
+            if( &_other != this ) {
+                reset();
+                if( _other )
+                    nullableValue = new( storage ) T( *_other );
+            }
+            return *this;
+        }
+        Option& operator = ( T const& _value ) {
+            reset();
+            nullableValue = new( storage ) T( _value );
+            return *this;
+        }
+
+        void reset() {
+            if( nullableValue )
+                nullableValue->~T();
+            nullableValue = nullptr;
+        }
+
+        T& operator*() { return *nullableValue; }
+        T const& operator*() const { return *nullableValue; }
+        T* operator->() { return nullableValue; }
+        const T* operator->() const { return nullableValue; }
+
+        T valueOr( T const& defaultValue ) const {
+            return nullableValue ? *nullableValue : defaultValue;
+        }
+
+        bool some() const { return nullableValue != nullptr; }
+        bool none() const { return nullableValue == nullptr; }
+
+        bool operator !() const { return nullableValue == nullptr; }
+        explicit operator bool() const {
+            return some();
+        }
+
+    private:
+        T *nullableValue;
+        alignas(alignof(T)) char storage[sizeof(T)];
+    };
+
+} // end namespace Catch
+
+// end catch_option.hpp
+#include <string>
+#include <iosfwd>
+#include <map>
+#include <set>
+#include <memory>
+
+namespace Catch {
+
+    struct ReporterConfig {
+        explicit ReporterConfig( IConfigPtr const& _fullConfig );
+
+        ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream );
+
+        std::ostream& stream() const;
+        IConfigPtr fullConfig() const;
+
+    private:
+        std::ostream* m_stream;
+        IConfigPtr m_fullConfig;
+    };
+
+    struct ReporterPreferences {
+        bool shouldRedirectStdOut = false;
+        bool shouldReportAllAssertions = false;
+    };
+
+    template<typename T>
+    struct LazyStat : Option<T> {
+        LazyStat& operator=( T const& _value ) {
+            Option<T>::operator=( _value );
+            used = false;
+            return *this;
+        }
+        void reset() {
+            Option<T>::reset();
+            used = false;
+        }
+        bool used = false;
+    };
+
+    struct TestRunInfo {
+        TestRunInfo( std::string const& _name );
+        std::string name;
+    };
+    struct GroupInfo {
+        GroupInfo(  std::string const& _name,
+                    std::size_t _groupIndex,
+                    std::size_t _groupsCount );
+
+        std::string name;
+        std::size_t groupIndex;
+        std::size_t groupsCounts;
+    };
+
+    struct AssertionStats {
+        AssertionStats( AssertionResult const& _assertionResult,
+                        std::vector<MessageInfo> const& _infoMessages,
+                        Totals const& _totals );
+
+        AssertionStats( AssertionStats const& )              = default;
+        AssertionStats( AssertionStats && )                  = default;
+        AssertionStats& operator = ( AssertionStats const& ) = default;
+        AssertionStats& operator = ( AssertionStats && )     = default;
+        virtual ~AssertionStats();
+
+        AssertionResult assertionResult;
+        std::vector<MessageInfo> infoMessages;
+        Totals totals;
+    };
+
+    struct SectionStats {
+        SectionStats(   SectionInfo const& _sectionInfo,
+                        Counts const& _assertions,
+                        double _durationInSeconds,
+                        bool _missingAssertions );
+        SectionStats( SectionStats const& )              = default;
+        SectionStats( SectionStats && )                  = default;
+        SectionStats& operator = ( SectionStats const& ) = default;
+        SectionStats& operator = ( SectionStats && )     = default;
+        virtual ~SectionStats();
+
+        SectionInfo sectionInfo;
+        Counts assertions;
+        double durationInSeconds;
+        bool missingAssertions;
+    };
+
+    struct TestCaseStats {
+        TestCaseStats(  TestCaseInfo const& _testInfo,
+                        Totals const& _totals,
+                        std::string const& _stdOut,
+                        std::string const& _stdErr,
+                        bool _aborting );
+
+        TestCaseStats( TestCaseStats const& )              = default;
+        TestCaseStats( TestCaseStats && )                  = default;
+        TestCaseStats& operator = ( TestCaseStats const& ) = default;
+        TestCaseStats& operator = ( TestCaseStats && )     = default;
+        virtual ~TestCaseStats();
+
+        TestCaseInfo testInfo;
+        Totals totals;
+        std::string stdOut;
+        std::string stdErr;
+        bool aborting;
+    };
+
+    struct TestGroupStats {
+        TestGroupStats( GroupInfo const& _groupInfo,
+                        Totals const& _totals,
+                        bool _aborting );
+        TestGroupStats( GroupInfo const& _groupInfo );
+
+        TestGroupStats( TestGroupStats const& )              = default;
+        TestGroupStats( TestGroupStats && )                  = default;
+        TestGroupStats& operator = ( TestGroupStats const& ) = default;
+        TestGroupStats& operator = ( TestGroupStats && )     = default;
+        virtual ~TestGroupStats();
+
+        GroupInfo groupInfo;
+        Totals totals;
+        bool aborting;
+    };
+
+    struct TestRunStats {
+        TestRunStats(   TestRunInfo const& _runInfo,
+                        Totals const& _totals,
+                        bool _aborting );
+
+        TestRunStats( TestRunStats const& )              = default;
+        TestRunStats( TestRunStats && )                  = default;
+        TestRunStats& operator = ( TestRunStats const& ) = default;
+        TestRunStats& operator = ( TestRunStats && )     = default;
+        virtual ~TestRunStats();
+
+        TestRunInfo runInfo;
+        Totals totals;
+        bool aborting;
+    };
+
+    struct BenchmarkInfo {
+        std::string name;
+    };
+    struct BenchmarkStats {
+        BenchmarkInfo info;
+        std::size_t iterations;
+        uint64_t elapsedTimeInNanoseconds;
+    };
+
+    struct IStreamingReporter {
+        virtual ~IStreamingReporter() = default;
+
+        // Implementing class must also provide the following static methods:
+        // static std::string getDescription();
+        // static std::set<Verbosity> getSupportedVerbosities()
+
+        virtual ReporterPreferences getPreferences() const = 0;
+
+        virtual void noMatchingTestCases( std::string const& spec ) = 0;
+
+        virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0;
+        virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0;
+
+        virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0;
+        virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0;
+
+        // *** experimental ***
+        virtual void benchmarkStarting( BenchmarkInfo const& ) {}
+
+        virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0;
+
+        // The return value indicates if the messages buffer should be cleared:
+        virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0;
+
+        // *** experimental ***
+        virtual void benchmarkEnded( BenchmarkStats const& ) {}
+
+        virtual void sectionEnded( SectionStats const& sectionStats ) = 0;
+        virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0;
+        virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0;
+        virtual void testRunEnded( TestRunStats const& testRunStats ) = 0;
+
+        virtual void skipTest( TestCaseInfo const& testInfo ) = 0;
+
+        // Default empty implementation provided
+        virtual void fatalErrorEncountered( StringRef name );
+
+        virtual bool isMulti() const;
+    };
+    using IStreamingReporterPtr = std::unique_ptr<IStreamingReporter>;
+
+    struct IReporterFactory {
+        virtual ~IReporterFactory();
+        virtual IStreamingReporterPtr create( ReporterConfig const& config ) const = 0;
+        virtual std::string getDescription() const = 0;
+    };
+    using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>;
+
+    struct IReporterRegistry {
+        using FactoryMap = std::map<std::string, IReporterFactoryPtr>;
+        using Listeners = std::vector<IReporterFactoryPtr>;
+
+        virtual ~IReporterRegistry();
+        virtual IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const = 0;
+        virtual FactoryMap const& getFactories() const = 0;
+        virtual Listeners const& getListeners() const = 0;
+    };
+
+} // end namespace Catch
+
+// end catch_interfaces_reporter.h
+#include <algorithm>
+#include <cstring>
+#include <cfloat>
+#include <cstdio>
+#include <cassert>
+#include <memory>
+#include <ostream>
+
+namespace Catch {
+    void prepareExpandedExpression(AssertionResult& result);
+
+    // Returns double formatted as %.3f (format expected on output)
+    std::string getFormattedDuration( double duration );
+
+    template<typename DerivedT>
+    struct StreamingReporterBase : IStreamingReporter {
+
+        StreamingReporterBase( ReporterConfig const& _config )
+        :   m_config( _config.fullConfig() ),
+            stream( _config.stream() )
+        {
+            m_reporterPrefs.shouldRedirectStdOut = false;
+            if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) )
+                CATCH_ERROR( "Verbosity level not supported by this reporter" );
+        }
+
+        ReporterPreferences getPreferences() const override {
+            return m_reporterPrefs;
+        }
+
+        static std::set<Verbosity> getSupportedVerbosities() {
+            return { Verbosity::Normal };
+        }
+
+        ~StreamingReporterBase() override = default;
+
+        void noMatchingTestCases(std::string const&) override {}
+
+        void testRunStarting(TestRunInfo const& _testRunInfo) override {
+            currentTestRunInfo = _testRunInfo;
+        }
+        void testGroupStarting(GroupInfo const& _groupInfo) override {
+            currentGroupInfo = _groupInfo;
+        }
+
+        void testCaseStarting(TestCaseInfo const& _testInfo) override  {
+            currentTestCaseInfo = _testInfo;
+        }
+        void sectionStarting(SectionInfo const& _sectionInfo) override {
+            m_sectionStack.push_back(_sectionInfo);
+        }
+
+        void sectionEnded(SectionStats const& /* _sectionStats */) override {
+            m_sectionStack.pop_back();
+        }
+        void testCaseEnded(TestCaseStats const& /* _testCaseStats */) override {
+            currentTestCaseInfo.reset();
+        }
+        void testGroupEnded(TestGroupStats const& /* _testGroupStats */) override {
+            currentGroupInfo.reset();
+        }
+        void testRunEnded(TestRunStats const& /* _testRunStats */) override {
+            currentTestCaseInfo.reset();
+            currentGroupInfo.reset();
+            currentTestRunInfo.reset();
+        }
+
+        void skipTest(TestCaseInfo const&) override {
+            // Don't do anything with this by default.
+            // It can optionally be overridden in the derived class.
+        }
+
+        IConfigPtr m_config;
+        std::ostream& stream;
+
+        LazyStat<TestRunInfo> currentTestRunInfo;
+        LazyStat<GroupInfo> currentGroupInfo;
+        LazyStat<TestCaseInfo> currentTestCaseInfo;
+
+        std::vector<SectionInfo> m_sectionStack;
+        ReporterPreferences m_reporterPrefs;
+    };
+
+    template<typename DerivedT>
+    struct CumulativeReporterBase : IStreamingReporter {
+        template<typename T, typename ChildNodeT>
+        struct Node {
+            explicit Node( T const& _value ) : value( _value ) {}
+            virtual ~Node() {}
+
+            using ChildNodes = std::vector<std::shared_ptr<ChildNodeT>>;
+            T value;
+            ChildNodes children;
+        };
+        struct SectionNode {
+            explicit SectionNode(SectionStats const& _stats) : stats(_stats) {}
+            virtual ~SectionNode() = default;
+
+            bool operator == (SectionNode const& other) const {
+                return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo;
+            }
+            bool operator == (std::shared_ptr<SectionNode> const& other) const {
+                return operator==(*other);
+            }
+
+            SectionStats stats;
+            using ChildSections = std::vector<std::shared_ptr<SectionNode>>;
+            using Assertions = std::vector<AssertionStats>;
+            ChildSections childSections;
+            Assertions assertions;
+            std::string stdOut;
+            std::string stdErr;
+        };
+
+        struct BySectionInfo {
+            BySectionInfo( SectionInfo const& other ) : m_other( other ) {}
+            BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {}
+            bool operator() (std::shared_ptr<SectionNode> const& node) const {
+                return ((node->stats.sectionInfo.name == m_other.name) &&
+                        (node->stats.sectionInfo.lineInfo == m_other.lineInfo));
+            }
+            void operator=(BySectionInfo const&) = delete;
+
+        private:
+            SectionInfo const& m_other;
+        };
+
+        using TestCaseNode = Node<TestCaseStats, SectionNode>;
+        using TestGroupNode = Node<TestGroupStats, TestCaseNode>;
+        using TestRunNode = Node<TestRunStats, TestGroupNode>;
+
+        CumulativeReporterBase( ReporterConfig const& _config )
+        :   m_config( _config.fullConfig() ),
+            stream( _config.stream() )
+        {
+            m_reporterPrefs.shouldRedirectStdOut = false;
+            if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) )
+                CATCH_ERROR( "Verbosity level not supported by this reporter" );
+        }
+        ~CumulativeReporterBase() override = default;
+
+        ReporterPreferences getPreferences() const override {
+            return m_reporterPrefs;
+        }
+
+        static std::set<Verbosity> getSupportedVerbosities() {
+            return { Verbosity::Normal };
+        }
+
+        void testRunStarting( TestRunInfo const& ) override {}
+        void testGroupStarting( GroupInfo const& ) override {}
+
+        void testCaseStarting( TestCaseInfo const& ) override {}
+
+        void sectionStarting( SectionInfo const& sectionInfo ) override {
+            SectionStats incompleteStats( sectionInfo, Counts(), 0, false );
+            std::shared_ptr<SectionNode> node;
+            if( m_sectionStack.empty() ) {
+                if( !m_rootSection )
+                    m_rootSection = std::make_shared<SectionNode>( incompleteStats );
+                node = m_rootSection;
+            }
+            else {
+                SectionNode& parentNode = *m_sectionStack.back();
+                auto it =
+                    std::find_if(   parentNode.childSections.begin(),
+                                    parentNode.childSections.end(),
+                                    BySectionInfo( sectionInfo ) );
+                if( it == parentNode.childSections.end() ) {
+                    node = std::make_shared<SectionNode>( incompleteStats );
+                    parentNode.childSections.push_back( node );
+                }
+                else
+                    node = *it;
+            }
+            m_sectionStack.push_back( node );
+            m_deepestSection = std::move(node);
+        }
+
+        void assertionStarting(AssertionInfo const&) override {}
+
+        bool assertionEnded(AssertionStats const& assertionStats) override {
+            assert(!m_sectionStack.empty());
+            // AssertionResult holds a pointer to a temporary DecomposedExpression,
+            // which getExpandedExpression() calls to build the expression string.
+            // Our section stack copy of the assertionResult will likely outlive the
+            // temporary, so it must be expanded or discarded now to avoid calling
+            // a destroyed object later.
+            prepareExpandedExpression(const_cast<AssertionResult&>( assertionStats.assertionResult ) );
+            SectionNode& sectionNode = *m_sectionStack.back();
+            sectionNode.assertions.push_back(assertionStats);
+            return true;
+        }
+        void sectionEnded(SectionStats const& sectionStats) override {
+            assert(!m_sectionStack.empty());
+            SectionNode& node = *m_sectionStack.back();
+            node.stats = sectionStats;
+            m_sectionStack.pop_back();
+        }
+        void testCaseEnded(TestCaseStats const& testCaseStats) override {
+            auto node = std::make_shared<TestCaseNode>(testCaseStats);
+            assert(m_sectionStack.size() == 0);
+            node->children.push_back(m_rootSection);
+            m_testCases.push_back(node);
+            m_rootSection.reset();
+
+            assert(m_deepestSection);
+            m_deepestSection->stdOut = testCaseStats.stdOut;
+            m_deepestSection->stdErr = testCaseStats.stdErr;
+        }
+        void testGroupEnded(TestGroupStats const& testGroupStats) override {
+            auto node = std::make_shared<TestGroupNode>(testGroupStats);
+            node->children.swap(m_testCases);
+            m_testGroups.push_back(node);
+        }
+        void testRunEnded(TestRunStats const& testRunStats) override {
+            auto node = std::make_shared<TestRunNode>(testRunStats);
+            node->children.swap(m_testGroups);
+            m_testRuns.push_back(node);
+            testRunEndedCumulative();
+        }
+        virtual void testRunEndedCumulative() = 0;
+
+        void skipTest(TestCaseInfo const&) override {}
+
+        IConfigPtr m_config;
+        std::ostream& stream;
+        std::vector<AssertionStats> m_assertions;
+        std::vector<std::vector<std::shared_ptr<SectionNode>>> m_sections;
+        std::vector<std::shared_ptr<TestCaseNode>> m_testCases;
+        std::vector<std::shared_ptr<TestGroupNode>> m_testGroups;
+
+        std::vector<std::shared_ptr<TestRunNode>> m_testRuns;
+
+        std::shared_ptr<SectionNode> m_rootSection;
+        std::shared_ptr<SectionNode> m_deepestSection;
+        std::vector<std::shared_ptr<SectionNode>> m_sectionStack;
+        ReporterPreferences m_reporterPrefs;
+    };
+
+    template<char C>
+    char const* getLineOfChars() {
+        static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0};
+        if( !*line ) {
+            std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 );
+            line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0;
+        }
+        return line;
+    }
+
+    struct TestEventListenerBase : StreamingReporterBase<TestEventListenerBase> {
+        TestEventListenerBase( ReporterConfig const& _config );
+
+        static std::set<Verbosity> getSupportedVerbosities();
+
+        void assertionStarting(AssertionInfo const&) override;
+        bool assertionEnded(AssertionStats const&) override;
+    };
+
+} // end namespace Catch
+
+// end catch_reporter_bases.hpp
+// start catch_console_colour.h
+
+namespace Catch {
+
+    struct Colour {
+        enum Code {
+            None = 0,
+
+            White,
+            Red,
+            Green,
+            Blue,
+            Cyan,
+            Yellow,
+            Grey,
+
+            Bright = 0x10,
+
+            BrightRed = Bright | Red,
+            BrightGreen = Bright | Green,
+            LightGrey = Bright | Grey,
+            BrightWhite = Bright | White,
+            BrightYellow = Bright | Yellow,
+
+            // By intention
+            FileName = LightGrey,
+            Warning = BrightYellow,
+            ResultError = BrightRed,
+            ResultSuccess = BrightGreen,
+            ResultExpectedFailure = Warning,
+
+            Error = BrightRed,
+            Success = Green,
+
+            OriginalExpression = Cyan,
+            ReconstructedExpression = BrightYellow,
+
+            SecondaryText = LightGrey,
+            Headers = White
+        };
+
+        // Use constructed object for RAII guard
+        Colour( Code _colourCode );
+        Colour( Colour&& other ) noexcept;
+        Colour& operator=( Colour&& other ) noexcept;
+        ~Colour();
+
+        // Use static method for one-shot changes
+        static void use( Code _colourCode );
+
+    private:
+        bool m_moved = false;
+    };
+
+    std::ostream& operator << ( std::ostream& os, Colour const& );
+
+} // end namespace Catch
+
+// end catch_console_colour.h
+// start catch_reporter_registrars.hpp
+
+
+namespace Catch {
+
+    template<typename T>
+    class ReporterRegistrar {
+
+        class ReporterFactory : public IReporterFactory {
+
+            virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override {
+                return std::unique_ptr<T>( new T( config ) );
+            }
+
+            virtual std::string getDescription() const override {
+                return T::getDescription();
+            }
+        };
+
+    public:
+
+        explicit ReporterRegistrar( std::string const& name ) {
+            getMutableRegistryHub().registerReporter( name, std::make_shared<ReporterFactory>() );
+        }
+    };
+
+    template<typename T>
+    class ListenerRegistrar {
+
+        class ListenerFactory : public IReporterFactory {
+
+            virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override {
+                return std::unique_ptr<T>( new T( config ) );
+            }
+            virtual std::string getDescription() const override {
+                return std::string();
+            }
+        };
+
+    public:
+
+        ListenerRegistrar() {
+            getMutableRegistryHub().registerListener( std::make_shared<ListenerFactory>() );
+        }
+    };
+}
+
+#if !defined(CATCH_CONFIG_DISABLE)
+
+#define CATCH_REGISTER_REPORTER( name, reporterType ) \
+    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS          \
+    namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); } \
+    CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS
+
+#define CATCH_REGISTER_LISTENER( listenerType ) \
+     CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS   \
+     namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; } \
+     CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
+#else // CATCH_CONFIG_DISABLE
+
+#define CATCH_REGISTER_REPORTER(name, reporterType)
+#define CATCH_REGISTER_LISTENER(listenerType)
+
+#endif // CATCH_CONFIG_DISABLE
+
+// end catch_reporter_registrars.hpp
+// Allow users to base their work off existing reporters
+// start catch_reporter_compact.h
+
+namespace Catch {
+
+    struct CompactReporter : StreamingReporterBase<CompactReporter> {
+
+        using StreamingReporterBase::StreamingReporterBase;
+
+        ~CompactReporter() override;
+
+        static std::string getDescription();
+
+        ReporterPreferences getPreferences() const override;
+
+        void noMatchingTestCases(std::string const& spec) override;
+
+        void assertionStarting(AssertionInfo const&) override;
+
+        bool assertionEnded(AssertionStats const& _assertionStats) override;
+
+        void sectionEnded(SectionStats const& _sectionStats) override;
+
+        void testRunEnded(TestRunStats const& _testRunStats) override;
+
+    };
+
+} // end namespace Catch
+
+// end catch_reporter_compact.h
+// start catch_reporter_console.h
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch
+                              // Note that 4062 (not all labels are handled
+                              // and default is missing) is enabled
+#endif
+
+namespace Catch {
+    // Fwd decls
+    struct SummaryColumn;
+    class TablePrinter;
+
+    struct ConsoleReporter : StreamingReporterBase<ConsoleReporter> {
+        std::unique_ptr<TablePrinter> m_tablePrinter;
+
+        ConsoleReporter(ReporterConfig const& config);
+        ~ConsoleReporter() override;
+        static std::string getDescription();
+
+        void noMatchingTestCases(std::string const& spec) override;
+
+        void assertionStarting(AssertionInfo const&) override;
+
+        bool assertionEnded(AssertionStats const& _assertionStats) override;
+
+        void sectionStarting(SectionInfo const& _sectionInfo) override;
+        void sectionEnded(SectionStats const& _sectionStats) override;
+
+        void benchmarkStarting(BenchmarkInfo const& info) override;
+        void benchmarkEnded(BenchmarkStats const& stats) override;
+
+        void testCaseEnded(TestCaseStats const& _testCaseStats) override;
+        void testGroupEnded(TestGroupStats const& _testGroupStats) override;
+        void testRunEnded(TestRunStats const& _testRunStats) override;
+
+    private:
+
+        void lazyPrint();
+
+        void lazyPrintWithoutClosingBenchmarkTable();
+        void lazyPrintRunInfo();
+        void lazyPrintGroupInfo();
+        void printTestCaseAndSectionHeader();
+
+        void printClosedHeader(std::string const& _name);
+        void printOpenHeader(std::string const& _name);
+
+        // if string has a : in first line will set indent to follow it on
+        // subsequent lines
+        void printHeaderString(std::string const& _string, std::size_t indent = 0);
+
+        void printTotals(Totals const& totals);
+        void printSummaryRow(std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row);
+
+        void printTotalsDivider(Totals const& totals);
+        void printSummaryDivider();
+
+    private:
+        bool m_headerPrinted = false;
+    };
+
+} // end namespace Catch
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+// end catch_reporter_console.h
+// start catch_reporter_junit.h
+
+// start catch_xmlwriter.h
+
+#include <vector>
+
+namespace Catch {
+
+    class XmlEncode {
+    public:
+        enum ForWhat { ForTextNodes, ForAttributes };
+
+        XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes );
+
+        void encodeTo( std::ostream& os ) const;
+
+        friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode );
+
+    private:
+        std::string m_str;
+        ForWhat m_forWhat;
+    };
+
+    class XmlWriter {
+    public:
+
+        class ScopedElement {
+        public:
+            ScopedElement( XmlWriter* writer );
+
+            ScopedElement( ScopedElement&& other ) noexcept;
+            ScopedElement& operator=( ScopedElement&& other ) noexcept;
+
+            ~ScopedElement();
+
+            ScopedElement& writeText( std::string const& text, bool indent = true );
+
+            template<typename T>
+            ScopedElement& writeAttribute( std::string const& name, T const& attribute ) {
+                m_writer->writeAttribute( name, attribute );
+                return *this;
+            }
+
+        private:
+            mutable XmlWriter* m_writer = nullptr;
+        };
+
+        XmlWriter( std::ostream& os = Catch::cout() );
+        ~XmlWriter();
+
+        XmlWriter( XmlWriter const& ) = delete;
+        XmlWriter& operator=( XmlWriter const& ) = delete;
+
+        XmlWriter& startElement( std::string const& name );
+
+        ScopedElement scopedElement( std::string const& name );
+
+        XmlWriter& endElement();
+
+        XmlWriter& writeAttribute( std::string const& name, std::string const& attribute );
+
+        XmlWriter& writeAttribute( std::string const& name, bool attribute );
+
+        template<typename T>
+        XmlWriter& writeAttribute( std::string const& name, T const& attribute ) {
+            ReusableStringStream rss;
+            rss << attribute;
+            return writeAttribute( name, rss.str() );
+        }
+
+        XmlWriter& writeText( std::string const& text, bool indent = true );
+
+        XmlWriter& writeComment( std::string const& text );
+
+        void writeStylesheetRef( std::string const& url );
+
+        XmlWriter& writeBlankLine();
+
+        void ensureTagClosed();
+
+    private:
+
+        void writeDeclaration();
+
+        void newlineIfNecessary();
+
+        bool m_tagIsOpen = false;
+        bool m_needsNewline = false;
+        std::vector<std::string> m_tags;
+        std::string m_indent;
+        std::ostream& m_os;
+    };
+
+}
+
+// end catch_xmlwriter.h
+namespace Catch {
+
+    class JunitReporter : public CumulativeReporterBase<JunitReporter> {
+    public:
+        JunitReporter(ReporterConfig const& _config);
+
+        ~JunitReporter() override;
+
+        static std::string getDescription();
+
+        void noMatchingTestCases(std::string const& /*spec*/) override;
+
+        void testRunStarting(TestRunInfo const& runInfo) override;
+
+        void testGroupStarting(GroupInfo const& groupInfo) override;
+
+        void testCaseStarting(TestCaseInfo const& testCaseInfo) override;
+        bool assertionEnded(AssertionStats const& assertionStats) override;
+
+        void testCaseEnded(TestCaseStats const& testCaseStats) override;
+
+        void testGroupEnded(TestGroupStats const& testGroupStats) override;
+
+        void testRunEndedCumulative() override;
+
+        void writeGroup(TestGroupNode const& groupNode, double suiteTime);
+
+        void writeTestCase(TestCaseNode const& testCaseNode);
+
+        void writeSection(std::string const& className,
+                          std::string const& rootName,
+                          SectionNode const& sectionNode);
+
+        void writeAssertions(SectionNode const& sectionNode);
+        void writeAssertion(AssertionStats const& stats);
+
+        XmlWriter xml;
+        Timer suiteTimer;
+        std::string stdOutForSuite;
+        std::string stdErrForSuite;
+        unsigned int unexpectedExceptions = 0;
+        bool m_okToFail = false;
+    };
+
+} // end namespace Catch
+
+// end catch_reporter_junit.h
+// start catch_reporter_xml.h
+
+namespace Catch {
+    class XmlReporter : public StreamingReporterBase<XmlReporter> {
+    public:
+        XmlReporter(ReporterConfig const& _config);
+
+        ~XmlReporter() override;
+
+        static std::string getDescription();
+
+        virtual std::string getStylesheetRef() const;
+
+        void writeSourceInfo(SourceLineInfo const& sourceInfo);
+
+    public: // StreamingReporterBase
+
+        void noMatchingTestCases(std::string const& s) override;
+
+        void testRunStarting(TestRunInfo const& testInfo) override;
+
+        void testGroupStarting(GroupInfo const& groupInfo) override;
+
+        void testCaseStarting(TestCaseInfo const& testInfo) override;
+
+        void sectionStarting(SectionInfo const& sectionInfo) override;
+
+        void assertionStarting(AssertionInfo const&) override;
+
+        bool assertionEnded(AssertionStats const& assertionStats) override;
+
+        void sectionEnded(SectionStats const& sectionStats) override;
+
+        void testCaseEnded(TestCaseStats const& testCaseStats) override;
+
+        void testGroupEnded(TestGroupStats const& testGroupStats) override;
+
+        void testRunEnded(TestRunStats const& testRunStats) override;
+
+    private:
+        Timer m_testCaseTimer;
+        XmlWriter m_xml;
+        int m_sectionDepth = 0;
+    };
+
+} // end namespace Catch
+
+// end catch_reporter_xml.h
+
+// end catch_external_interfaces.h
+#endif
+
+#endif // ! CATCH_CONFIG_IMPL_ONLY
+
+#ifdef CATCH_IMPL
+// start catch_impl.hpp
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wweak-vtables"
+#endif
+
+// Keep these here for external reporters
+// start catch_test_case_tracker.h
+
+#include <string>
+#include <vector>
+#include <memory>
+
+namespace Catch {
+namespace TestCaseTracking {
+
+    struct NameAndLocation {
+        std::string name;
+        SourceLineInfo location;
+
+        NameAndLocation( std::string const& _name, SourceLineInfo const& _location );
+    };
+
+    struct ITracker;
+
+    using ITrackerPtr = std::shared_ptr<ITracker>;
+
+    struct ITracker {
+        virtual ~ITracker();
+
+        // static queries
+        virtual NameAndLocation const& nameAndLocation() const = 0;
+
+        // dynamic queries
+        virtual bool isComplete() const = 0; // Successfully completed or failed
+        virtual bool isSuccessfullyCompleted() const = 0;
+        virtual bool isOpen() const = 0; // Started but not complete
+        virtual bool hasChildren() const = 0;
+
+        virtual ITracker& parent() = 0;
+
+        // actions
+        virtual void close() = 0; // Successfully complete
+        virtual void fail() = 0;
+        virtual void markAsNeedingAnotherRun() = 0;
+
+        virtual void addChild( ITrackerPtr const& child ) = 0;
+        virtual ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) = 0;
+        virtual void openChild() = 0;
+
+        // Debug/ checking
+        virtual bool isSectionTracker() const = 0;
+        virtual bool isIndexTracker() const = 0;
+    };
+
+    class TrackerContext {
+
+        enum RunState {
+            NotStarted,
+            Executing,
+            CompletedCycle
+        };
+
+        ITrackerPtr m_rootTracker;
+        ITracker* m_currentTracker = nullptr;
+        RunState m_runState = NotStarted;
+
+    public:
+
+        static TrackerContext& instance();
+
+        ITracker& startRun();
+        void endRun();
+
+        void startCycle();
+        void completeCycle();
+
+        bool completedCycle() const;
+        ITracker& currentTracker();
+        void setCurrentTracker( ITracker* tracker );
+    };
+
+    class TrackerBase : public ITracker {
+    protected:
+        enum CycleState {
+            NotStarted,
+            Executing,
+            ExecutingChildren,
+            NeedsAnotherRun,
+            CompletedSuccessfully,
+            Failed
+        };
+
+        using Children = std::vector<ITrackerPtr>;
+        NameAndLocation m_nameAndLocation;
+        TrackerContext& m_ctx;
+        ITracker* m_parent;
+        Children m_children;
+        CycleState m_runState = NotStarted;
+
+    public:
+        TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent );
+
+        NameAndLocation const& nameAndLocation() const override;
+        bool isComplete() const override;
+        bool isSuccessfullyCompleted() const override;
+        bool isOpen() const override;
+        bool hasChildren() const override;
+
+        void addChild( ITrackerPtr const& child ) override;
+
+        ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) override;
+        ITracker& parent() override;
+
+        void openChild() override;
+
+        bool isSectionTracker() const override;
+        bool isIndexTracker() const override;
+
+        void open();
+
+        void close() override;
+        void fail() override;
+        void markAsNeedingAnotherRun() override;
+
+    private:
+        void moveToParent();
+        void moveToThis();
+    };
+
+    class SectionTracker : public TrackerBase {
+        std::vector<std::string> m_filters;
+    public:
+        SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent );
+
+        bool isSectionTracker() const override;
+
+        static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation );
+
+        void tryOpen();
+
+        void addInitialFilters( std::vector<std::string> const& filters );
+        void addNextFilters( std::vector<std::string> const& filters );
+    };
+
+    class IndexTracker : public TrackerBase {
+        int m_size;
+        int m_index = -1;
+    public:
+        IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size );
+
+        bool isIndexTracker() const override;
+        void close() override;
+
+        static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size );
+
+        int index() const;
+
+        void moveNext();
+    };
+
+} // namespace TestCaseTracking
+
+using TestCaseTracking::ITracker;
+using TestCaseTracking::TrackerContext;
+using TestCaseTracking::SectionTracker;
+using TestCaseTracking::IndexTracker;
+
+} // namespace Catch
+
+// end catch_test_case_tracker.h
+
+// start catch_leak_detector.h
+
+namespace Catch {
+
+    struct LeakDetector {
+        LeakDetector();
+        ~LeakDetector();
+    };
+
+}
+// end catch_leak_detector.h
+// Cpp files will be included in the single-header file here
+// start catch_approx.cpp
+
+#include <cmath>
+#include <limits>
+
+namespace {
+
+// Performs equivalent check of std::fabs(lhs - rhs) <= margin
+// But without the subtraction to allow for INFINITY in comparison
+bool marginComparison(double lhs, double rhs, double margin) {
+    return (lhs + margin >= rhs) && (rhs + margin >= lhs);
+}
+
+}
+
+namespace Catch {
+namespace Detail {
+
+    Approx::Approx ( double value )
+    :   m_epsilon( std::numeric_limits<float>::epsilon()*100 ),
+        m_margin( 0.0 ),
+        m_scale( 0.0 ),
+        m_value( value )
+    {}
+
+    Approx Approx::custom() {
+        return Approx( 0 );
+    }
+
+    Approx Approx::operator-() const {
+        auto temp(*this);
+        temp.m_value = -temp.m_value;
+        return temp;
+    }
+
+    std::string Approx::toString() const {
+        ReusableStringStream rss;
+        rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )";
+        return rss.str();
+    }
+
+    bool Approx::equalityComparisonImpl(const double other) const {
+        // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value
+        // Thanks to Richard Harris for his help refining the scaled margin value
+        return marginComparison(m_value, other, m_margin) || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(m_value)));
+    }
+
+    void Approx::setMargin(double margin) {
+        CATCH_ENFORCE(margin >= 0,
+            "Invalid Approx::margin: " << margin << '.'
+            << " Approx::Margin has to be non-negative.");
+        m_margin = margin;
+    }
+
+    void Approx::setEpsilon(double epsilon) {
+        CATCH_ENFORCE(epsilon >= 0 && epsilon <= 1.0,
+            "Invalid Approx::epsilon: " << epsilon << '.'
+            << " Approx::epsilon has to be in [0, 1]");
+        m_epsilon = epsilon;
+    }
+
+} // end namespace Detail
+
+namespace literals {
+    Detail::Approx operator "" _a(long double val) {
+        return Detail::Approx(val);
+    }
+    Detail::Approx operator "" _a(unsigned long long val) {
+        return Detail::Approx(val);
+    }
+} // end namespace literals
+
+std::string StringMaker<Catch::Detail::Approx>::convert(Catch::Detail::Approx const& value) {
+    return value.toString();
+}
+
+} // end namespace Catch
+// end catch_approx.cpp
+// start catch_assertionhandler.cpp
+
+// start catch_context.h
+
+#include <memory>
+
+namespace Catch {
+
+    struct IResultCapture;
+    struct IRunner;
+    struct IConfig;
+    struct IMutableContext;
+
+    using IConfigPtr = std::shared_ptr<IConfig const>;
+
+    struct IContext
+    {
+        virtual ~IContext();
+
+        virtual IResultCapture* getResultCapture() = 0;
+        virtual IRunner* getRunner() = 0;
+        virtual IConfigPtr const& getConfig() const = 0;
+    };
+
+    struct IMutableContext : IContext
+    {
+        virtual ~IMutableContext();
+        virtual void setResultCapture( IResultCapture* resultCapture ) = 0;
+        virtual void setRunner( IRunner* runner ) = 0;
+        virtual void setConfig( IConfigPtr const& config ) = 0;
+
+    private:
+        static IMutableContext *currentContext;
+        friend IMutableContext& getCurrentMutableContext();
+        friend void cleanUpContext();
+        static void createContext();
+    };
+
+    inline IMutableContext& getCurrentMutableContext()
+    {
+        if( !IMutableContext::currentContext )
+            IMutableContext::createContext();
+        return *IMutableContext::currentContext;
+    }
+
+    inline IContext& getCurrentContext()
+    {
+        return getCurrentMutableContext();
+    }
+
+    void cleanUpContext();
+}
+
+// end catch_context.h
+// start catch_debugger.h
+
+namespace Catch {
+    bool isDebuggerActive();
+}
+
+#ifdef CATCH_PLATFORM_MAC
+
+    #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */
+
+#elif defined(CATCH_PLATFORM_LINUX)
+    // If we can use inline assembler, do it because this allows us to break
+    // directly at the location of the failing check instead of breaking inside
+    // raise() called from it, i.e. one stack frame below.
+    #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64))
+        #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */
+    #else // Fall back to the generic way.
+        #include <signal.h>
+
+        #define CATCH_TRAP() raise(SIGTRAP)
+    #endif
+#elif defined(_MSC_VER)
+    #define CATCH_TRAP() __debugbreak()
+#elif defined(__MINGW32__)
+    extern "C" __declspec(dllimport) void __stdcall DebugBreak();
+    #define CATCH_TRAP() DebugBreak()
+#endif
+
+#ifdef CATCH_TRAP
+    #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); }
+#else
+    namespace Catch {
+        inline void doNothing() {}
+    }
+    #define CATCH_BREAK_INTO_DEBUGGER() Catch::doNothing()
+#endif
+
+// end catch_debugger.h
+// start catch_run_context.h
+
+// start catch_fatal_condition.h
+
+// start catch_windows_h_proxy.h
+
+
+#if defined(CATCH_PLATFORM_WINDOWS)
+
+#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX)
+#  define CATCH_DEFINED_NOMINMAX
+#  define NOMINMAX
+#endif
+#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN)
+#  define CATCH_DEFINED_WIN32_LEAN_AND_MEAN
+#  define WIN32_LEAN_AND_MEAN
+#endif
+
+#ifdef __AFXDLL
+#include <AfxWin.h>
+#else
+#include <windows.h>
+#endif
+
+#ifdef CATCH_DEFINED_NOMINMAX
+#  undef NOMINMAX
+#endif
+#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN
+#  undef WIN32_LEAN_AND_MEAN
+#endif
+
+#endif // defined(CATCH_PLATFORM_WINDOWS)
+
+// end catch_windows_h_proxy.h
+#if defined( CATCH_CONFIG_WINDOWS_SEH )
+
+namespace Catch {
+
+    struct FatalConditionHandler {
+
+        static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo);
+        FatalConditionHandler();
+        static void reset();
+        ~FatalConditionHandler();
+
+    private:
+        static bool isSet;
+        static ULONG guaranteeSize;
+        static PVOID exceptionHandlerHandle;
+    };
+
+} // namespace Catch
+
+#elif defined ( CATCH_CONFIG_POSIX_SIGNALS )
+
+#include <signal.h>
+
+namespace Catch {
+
+    struct FatalConditionHandler {
+
+        static bool isSet;
+        static struct sigaction oldSigActions[];
+        static stack_t oldSigStack;
+        static char altStackMem[];
+
+        static void handleSignal( int sig );
+
+        FatalConditionHandler();
+        ~FatalConditionHandler();
+        static void reset();
+    };
+
+} // namespace Catch
+
+#else
+
+namespace Catch {
+    struct FatalConditionHandler {
+        void reset();
+    };
+}
+
+#endif
+
+// end catch_fatal_condition.h
+#include <string>
+
+namespace Catch {
+
+    struct IMutableContext;
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    class RunContext : public IResultCapture, public IRunner {
+
+    public:
+        RunContext( RunContext const& ) = delete;
+        RunContext& operator =( RunContext const& ) = delete;
+
+        explicit RunContext( IConfigPtr const& _config, IStreamingReporterPtr&& reporter );
+
+        ~RunContext() override;
+
+        void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount );
+        void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount );
+
+        Totals runTest(TestCase const& testCase);
+
+        IConfigPtr config() const;
+        IStreamingReporter& reporter() const;
+
+    public: // IResultCapture
+
+        // Assertion handlers
+        void handleExpr
+                (   AssertionInfo const& info,
+                    ITransientExpression const& expr,
+                    AssertionReaction& reaction ) override;
+        void handleMessage
+                (   AssertionInfo const& info,
+                    ResultWas::OfType resultType,
+                    StringRef const& message,
+                    AssertionReaction& reaction ) override;
+        void handleUnexpectedExceptionNotThrown
+                (   AssertionInfo const& info,
+                    AssertionReaction& reaction ) override;
+        void handleUnexpectedInflightException
+                (   AssertionInfo const& info,
+                    std::string const& message,
+                    AssertionReaction& reaction ) override;
+        void handleIncomplete
+                (   AssertionInfo const& info ) override;
+        void handleNonExpr
+                (   AssertionInfo const &info,
+                    ResultWas::OfType resultType,
+                    AssertionReaction &reaction ) override;
+
+        bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) override;
+
+        void sectionEnded( SectionEndInfo const& endInfo ) override;
+        void sectionEndedEarly( SectionEndInfo const& endInfo ) override;
+
+        auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& override;
+
+        void benchmarkStarting( BenchmarkInfo const& info ) override;
+        void benchmarkEnded( BenchmarkStats const& stats ) override;
+
+        void pushScopedMessage( MessageInfo const& message ) override;
+        void popScopedMessage( MessageInfo const& message ) override;
+
+        std::string getCurrentTestName() const override;
+
+        const AssertionResult* getLastResult() const override;
+
+        void exceptionEarlyReported() override;
+
+        void handleFatalErrorCondition( StringRef message ) override;
+
+        bool lastAssertionPassed() override;
+
+        void assertionPassed() override;
+
+    public:
+        // !TBD We need to do this another way!
+        bool aborting() const final;
+
+    private:
+
+        void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr );
+        void invokeActiveTestCase();
+
+        void resetAssertionInfo();
+        bool testForMissingAssertions( Counts& assertions );
+
+        void assertionEnded( AssertionResult const& result );
+        void reportExpr
+                (   AssertionInfo const &info,
+                    ResultWas::OfType resultType,
+                    ITransientExpression const *expr,
+                    bool negated );
+
+        void populateReaction( AssertionReaction& reaction );
+
+    private:
+
+        void handleUnfinishedSections();
+
+        TestRunInfo m_runInfo;
+        IMutableContext& m_context;
+        TestCase const* m_activeTestCase = nullptr;
+        ITracker* m_testCaseTracker;
+        Option<AssertionResult> m_lastResult;
+
+        IConfigPtr m_config;
+        Totals m_totals;
+        IStreamingReporterPtr m_reporter;
+        std::vector<MessageInfo> m_messages;
+        AssertionInfo m_lastAssertionInfo;
+        std::vector<SectionEndInfo> m_unfinishedSections;
+        std::vector<ITracker*> m_activeSections;
+        TrackerContext m_trackerContext;
+        bool m_lastAssertionPassed = false;
+        bool m_shouldReportUnexpected = true;
+        bool m_includeSuccessfulResults;
+    };
+
+} // end namespace Catch
+
+// end catch_run_context.h
+namespace Catch {
+
+    namespace {
+        auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& {
+            expr.streamReconstructedExpression( os );
+            return os;
+        }
+    }
+
+    LazyExpression::LazyExpression( bool isNegated )
+    :   m_isNegated( isNegated )
+    {}
+
+    LazyExpression::LazyExpression( LazyExpression const& other ) : m_isNegated( other.m_isNegated ) {}
+
+    LazyExpression::operator bool() const {
+        return m_transientExpression != nullptr;
+    }
+
+    auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream& {
+        if( lazyExpr.m_isNegated )
+            os << "!";
+
+        if( lazyExpr ) {
+            if( lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression() )
+                os << "(" << *lazyExpr.m_transientExpression << ")";
+            else
+                os << *lazyExpr.m_transientExpression;
+        }
+        else {
+            os << "{** error - unchecked empty expression requested **}";
+        }
+        return os;
+    }
+
+    AssertionHandler::AssertionHandler
+        (   StringRef const& macroName,
+            SourceLineInfo const& lineInfo,
+            StringRef capturedExpression,
+            ResultDisposition::Flags resultDisposition )
+    :   m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition },
+        m_resultCapture( getResultCapture() )
+    {}
+
+    void AssertionHandler::handleExpr( ITransientExpression const& expr ) {
+        m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction );
+    }
+    void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef const& message) {
+        m_resultCapture.handleMessage( m_assertionInfo, resultType, message, m_reaction );
+    }
+
+    auto AssertionHandler::allowThrows() const -> bool {
+        return getCurrentContext().getConfig()->allowThrows();
+    }
+
+    void AssertionHandler::complete() {
+        setCompleted();
+        if( m_reaction.shouldDebugBreak ) {
+
+            // If you find your debugger stopping you here then go one level up on the
+            // call-stack for the code that caused it (typically a failed assertion)
+
+            // (To go back to the test and change execution, jump over the throw, next)
+            CATCH_BREAK_INTO_DEBUGGER();
+        }
+        if (m_reaction.shouldThrow) {
+#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
+            throw Catch::TestFailureException();
+#else
+            CATCH_ERROR( "Test failure requires aborting test!" );
+#endif
+        }
+    }
+    void AssertionHandler::setCompleted() {
+        m_completed = true;
+    }
+
+    void AssertionHandler::handleUnexpectedInflightException() {
+        m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction );
+    }
+
+    void AssertionHandler::handleExceptionThrownAsExpected() {
+        m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);
+    }
+    void AssertionHandler::handleExceptionNotThrownAsExpected() {
+        m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);
+    }
+
+    void AssertionHandler::handleUnexpectedExceptionNotThrown() {
+        m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction );
+    }
+
+    void AssertionHandler::handleThrowingCallSkipped() {
+        m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);
+    }
+
+    // This is the overload that takes a string and infers the Equals matcher from it
+    // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp
+    void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString  ) {
+        handleExceptionMatchExpr( handler, Matchers::Equals( str ), matcherString );
+    }
+
+} // namespace Catch
+// end catch_assertionhandler.cpp
+// start catch_assertionresult.cpp
+
+namespace Catch {
+    AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const & _lazyExpression):
+        lazyExpression(_lazyExpression),
+        resultType(_resultType) {}
+
+    std::string AssertionResultData::reconstructExpression() const {
+
+        if( reconstructedExpression.empty() ) {
+            if( lazyExpression ) {
+                ReusableStringStream rss;
+                rss << lazyExpression;
+                reconstructedExpression = rss.str();
+            }
+        }
+        return reconstructedExpression;
+    }
+
+    AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data )
+    :   m_info( info ),
+        m_resultData( data )
+    {}
+
+    // Result was a success
+    bool AssertionResult::succeeded() const {
+        return Catch::isOk( m_resultData.resultType );
+    }
+
+    // Result was a success, or failure is suppressed
+    bool AssertionResult::isOk() const {
+        return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition );
+    }
+
+    ResultWas::OfType AssertionResult::getResultType() const {
+        return m_resultData.resultType;
+    }
+
+    bool AssertionResult::hasExpression() const {
+        return m_info.capturedExpression[0] != 0;
+    }
+
+    bool AssertionResult::hasMessage() const {
+        return !m_resultData.message.empty();
+    }
+
+    std::string AssertionResult::getExpression() const {
+        if( isFalseTest( m_info.resultDisposition ) )
+            return "!(" + m_info.capturedExpression + ")";
+        else
+            return m_info.capturedExpression;
+    }
+
+    std::string AssertionResult::getExpressionInMacro() const {
+        std::string expr;
+        if( m_info.macroName[0] == 0 )
+            expr = m_info.capturedExpression;
+        else {
+            expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 );
+            expr += m_info.macroName;
+            expr += "( ";
+            expr += m_info.capturedExpression;
+            expr += " )";
+        }
+        return expr;
+    }
+
+    bool AssertionResult::hasExpandedExpression() const {
+        return hasExpression() && getExpandedExpression() != getExpression();
+    }
+
+    std::string AssertionResult::getExpandedExpression() const {
+        std::string expr = m_resultData.reconstructExpression();
+        return expr.empty()
+                ? getExpression()
+                : expr;
+    }
+
+    std::string AssertionResult::getMessage() const {
+        return m_resultData.message;
+    }
+    SourceLineInfo AssertionResult::getSourceInfo() const {
+        return m_info.lineInfo;
+    }
+
+    StringRef AssertionResult::getTestMacroName() const {
+        return m_info.macroName;
+    }
+
+} // end namespace Catch
+// end catch_assertionresult.cpp
+// start catch_benchmark.cpp
+
+namespace Catch {
+
+    auto BenchmarkLooper::getResolution() -> uint64_t {
+        return getEstimatedClockResolution() * getCurrentContext().getConfig()->benchmarkResolutionMultiple();
+    }
+
+    void BenchmarkLooper::reportStart() {
+        getResultCapture().benchmarkStarting( { m_name } );
+    }
+    auto BenchmarkLooper::needsMoreIterations() -> bool {
+        auto elapsed = m_timer.getElapsedNanoseconds();
+
+        // Exponentially increasing iterations until we're confident in our timer resolution
+        if( elapsed < m_resolution ) {
+            m_iterationsToRun *= 10;
+            return true;
+        }
+
+        getResultCapture().benchmarkEnded( { { m_name }, m_count, elapsed } );
+        return false;
+    }
+
+} // end namespace Catch
+// end catch_benchmark.cpp
+// start catch_capture_matchers.cpp
+
+namespace Catch {
+
+    using StringMatcher = Matchers::Impl::MatcherBase<std::string>;
+
+    // This is the general overload that takes a any string matcher
+    // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers
+    // the Equals matcher (so the header does not mention matchers)
+    void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString  ) {
+        std::string exceptionMessage = Catch::translateActiveException();
+        MatchExpr<std::string, StringMatcher const&> expr( exceptionMessage, matcher, matcherString );
+        handler.handleExpr( expr );
+    }
+
+} // namespace Catch
+// end catch_capture_matchers.cpp
+// start catch_commandline.cpp
+
+// start catch_commandline.h
+
+// start catch_clara.h
+
+// Use Catch's value for console width (store Clara's off to the side, if present)
+#ifdef CLARA_CONFIG_CONSOLE_WIDTH
+#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH
+#undef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH
+#endif
+#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH-1
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wweak-vtables"
+#pragma clang diagnostic ignored "-Wexit-time-destructors"
+#pragma clang diagnostic ignored "-Wshadow"
+#endif
+
+// start clara.hpp
+// Copyright 2017 Two Blue Cubes Ltd. All rights reserved.
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+// See https://github.com/philsquared/Clara for more details
+
+// Clara v1.1.5
+
+
+#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH
+#define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80
+#endif
+
+#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH
+#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH
+#endif
+
+#ifndef CLARA_CONFIG_OPTIONAL_TYPE
+#ifdef __has_include
+#if __has_include(<optional>) && __cplusplus >= 201703L
+#include <optional>
+#define CLARA_CONFIG_OPTIONAL_TYPE std::optional
+#endif
+#endif
+#endif
+
+// ----------- #included from clara_textflow.hpp -----------
+
+// TextFlowCpp
+//
+// A single-header library for wrapping and laying out basic text, by Phil Nash
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+// This project is hosted at https://github.com/philsquared/textflowcpp
+
+
+#include <cassert>
+#include <ostream>
+#include <sstream>
+#include <vector>
+
+#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH
+#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80
+#endif
+
+namespace Catch {
+namespace clara {
+namespace TextFlow {
+
+inline auto isWhitespace(char c) -> bool {
+	static std::string chars = " \t\n\r";
+	return chars.find(c) != std::string::npos;
+}
+inline auto isBreakableBefore(char c) -> bool {
+	static std::string chars = "[({<|";
+	return chars.find(c) != std::string::npos;
+}
+inline auto isBreakableAfter(char c) -> bool {
+	static std::string chars = "])}>.,:;*+-=&/\\";
+	return chars.find(c) != std::string::npos;
+}
+
+class Columns;
+
+class Column {
+	std::vector<std::string> m_strings;
+	size_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH;
+	size_t m_indent = 0;
+	size_t m_initialIndent = std::string::npos;
+
+public:
+	class iterator {
+		friend Column;
+
+		Column const& m_column;
+		size_t m_stringIndex = 0;
+		size_t m_pos = 0;
+
+		size_t m_len = 0;
+		size_t m_end = 0;
+		bool m_suffix = false;
+
+		iterator(Column const& column, size_t stringIndex)
+			: m_column(column),
+			m_stringIndex(stringIndex) {}
+
+		auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; }
+
+		auto isBoundary(size_t at) const -> bool {
+			assert(at > 0);
+			assert(at <= line().size());
+
+			return at == line().size() ||
+				(isWhitespace(line()[at]) && !isWhitespace(line()[at - 1])) ||
+				isBreakableBefore(line()[at]) ||
+				isBreakableAfter(line()[at - 1]);
+		}
+
+		void calcLength() {
+			assert(m_stringIndex < m_column.m_strings.size());
+
+			m_suffix = false;
+			auto width = m_column.m_width - indent();
+			m_end = m_pos;
+			while (m_end < line().size() && line()[m_end] != '\n')
+				++m_end;
+
+			if (m_end < m_pos + width) {
+				m_len = m_end - m_pos;
+			} else {
+				size_t len = width;
+				while (len > 0 && !isBoundary(m_pos + len))
+					--len;
+				while (len > 0 && isWhitespace(line()[m_pos + len - 1]))
+					--len;
+
+				if (len > 0) {
+					m_len = len;
+				} else {
+					m_suffix = true;
+					m_len = width - 1;
+				}
+			}
+		}
+
+		auto indent() const -> size_t {
+			auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos;
+			return initial == std::string::npos ? m_column.m_indent : initial;
+		}
+
+		auto addIndentAndSuffix(std::string const &plain) const -> std::string {
+			return std::string(indent(), ' ') + (m_suffix ? plain + "-" : plain);
+		}
+
+	public:
+		using difference_type = std::ptrdiff_t;
+		using value_type = std::string;
+		using pointer = value_type * ;
+		using reference = value_type & ;
+		using iterator_category = std::forward_iterator_tag;
+
+		explicit iterator(Column const& column) : m_column(column) {
+			assert(m_column.m_width > m_column.m_indent);
+			assert(m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent);
+			calcLength();
+			if (m_len == 0)
+				m_stringIndex++; // Empty string
+		}
+
+		auto operator *() const -> std::string {
+			assert(m_stringIndex < m_column.m_strings.size());
+			assert(m_pos <= m_end);
+			return addIndentAndSuffix(line().substr(m_pos, m_len));
+		}
+
+		auto operator ++() -> iterator& {
+			m_pos += m_len;
+			if (m_pos < line().size() && line()[m_pos] == '\n')
+				m_pos += 1;
+			else
+				while (m_pos < line().size() && isWhitespace(line()[m_pos]))
+					++m_pos;
+
+			if (m_pos == line().size()) {
+				m_pos = 0;
+				++m_stringIndex;
+			}
+			if (m_stringIndex < m_column.m_strings.size())
+				calcLength();
+			return *this;
+		}
+		auto operator ++(int) -> iterator {
+			iterator prev(*this);
+			operator++();
+			return prev;
+		}
+
+		auto operator ==(iterator const& other) const -> bool {
+			return
+				m_pos == other.m_pos &&
+				m_stringIndex == other.m_stringIndex &&
+				&m_column == &other.m_column;
+		}
+		auto operator !=(iterator const& other) const -> bool {
+			return !operator==(other);
+		}
+	};
+	using const_iterator = iterator;
+
+	explicit Column(std::string const& text) { m_strings.push_back(text); }
+
+	auto width(size_t newWidth) -> Column& {
+		assert(newWidth > 0);
+		m_width = newWidth;
+		return *this;
+	}
+	auto indent(size_t newIndent) -> Column& {
+		m_indent = newIndent;
+		return *this;
+	}
+	auto initialIndent(size_t newIndent) -> Column& {
+		m_initialIndent = newIndent;
+		return *this;
+	}
+
+	auto width() const -> size_t { return m_width; }
+	auto begin() const -> iterator { return iterator(*this); }
+	auto end() const -> iterator { return { *this, m_strings.size() }; }
+
+	inline friend std::ostream& operator << (std::ostream& os, Column const& col) {
+		bool first = true;
+		for (auto line : col) {
+			if (first)
+				first = false;
+			else
+				os << "\n";
+			os << line;
+		}
+		return os;
+	}
+
+	auto operator + (Column const& other)->Columns;
+
+	auto toString() const -> std::string {
+		std::ostringstream oss;
+		oss << *this;
+		return oss.str();
+	}
+};
+
+class Spacer : public Column {
+
+public:
+	explicit Spacer(size_t spaceWidth) : Column("") {
+		width(spaceWidth);
+	}
+};
+
+class Columns {
+	std::vector<Column> m_columns;
+
+public:
+
+	class iterator {
+		friend Columns;
+		struct EndTag {};
+
+		std::vector<Column> const& m_columns;
+		std::vector<Column::iterator> m_iterators;
+		size_t m_activeIterators;
+
+		iterator(Columns const& columns, EndTag)
+			: m_columns(columns.m_columns),
+			m_activeIterators(0) {
+			m_iterators.reserve(m_columns.size());
+
+			for (auto const& col : m_columns)
+				m_iterators.push_back(col.end());
+		}
+
+	public:
+		using difference_type = std::ptrdiff_t;
+		using value_type = std::string;
+		using pointer = value_type * ;
+		using reference = value_type & ;
+		using iterator_category = std::forward_iterator_tag;
+
+		explicit iterator(Columns const& columns)
+			: m_columns(columns.m_columns),
+			m_activeIterators(m_columns.size()) {
+			m_iterators.reserve(m_columns.size());
+
+			for (auto const& col : m_columns)
+				m_iterators.push_back(col.begin());
+		}
+
+		auto operator ==(iterator const& other) const -> bool {
+			return m_iterators == other.m_iterators;
+		}
+		auto operator !=(iterator const& other) const -> bool {
+			return m_iterators != other.m_iterators;
+		}
+		auto operator *() const -> std::string {
+			std::string row, padding;
+
+			for (size_t i = 0; i < m_columns.size(); ++i) {
+				auto width = m_columns[i].width();
+				if (m_iterators[i] != m_columns[i].end()) {
+					std::string col = *m_iterators[i];
+					row += padding + col;
+					if (col.size() < width)
+						padding = std::string(width - col.size(), ' ');
+					else
+						padding = "";
+				} else {
+					padding += std::string(width, ' ');
+				}
+			}
+			return row;
+		}
+		auto operator ++() -> iterator& {
+			for (size_t i = 0; i < m_columns.size(); ++i) {
+				if (m_iterators[i] != m_columns[i].end())
+					++m_iterators[i];
+			}
+			return *this;
+		}
+		auto operator ++(int) -> iterator {
+			iterator prev(*this);
+			operator++();
+			return prev;
+		}
+	};
+	using const_iterator = iterator;
+
+	auto begin() const -> iterator { return iterator(*this); }
+	auto end() const -> iterator { return { *this, iterator::EndTag() }; }
+
+	auto operator += (Column const& col) -> Columns& {
+		m_columns.push_back(col);
+		return *this;
+	}
+	auto operator + (Column const& col) -> Columns {
+		Columns combined = *this;
+		combined += col;
+		return combined;
+	}
+
+	inline friend std::ostream& operator << (std::ostream& os, Columns const& cols) {
+
+		bool first = true;
+		for (auto line : cols) {
+			if (first)
+				first = false;
+			else
+				os << "\n";
+			os << line;
+		}
+		return os;
+	}
+
+	auto toString() const -> std::string {
+		std::ostringstream oss;
+		oss << *this;
+		return oss.str();
+	}
+};
+
+inline auto Column::operator + (Column const& other) -> Columns {
+	Columns cols;
+	cols += *this;
+	cols += other;
+	return cols;
+}
+}
+
+}
+}
+
+// ----------- end of #include from clara_textflow.hpp -----------
+// ........... back in clara.hpp
+
+#include <string>
+#include <memory>
+#include <set>
+#include <algorithm>
+
+#if !defined(CATCH_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) )
+#define CATCH_PLATFORM_WINDOWS
+#endif
+
+namespace Catch { namespace clara {
+namespace detail {
+
+    // Traits for extracting arg and return type of lambdas (for single argument lambdas)
+    template<typename L>
+    struct UnaryLambdaTraits : UnaryLambdaTraits<decltype( &L::operator() )> {};
+
+    template<typename ClassT, typename ReturnT, typename... Args>
+    struct UnaryLambdaTraits<ReturnT( ClassT::* )( Args... ) const> {
+        static const bool isValid = false;
+    };
+
+    template<typename ClassT, typename ReturnT, typename ArgT>
+    struct UnaryLambdaTraits<ReturnT( ClassT::* )( ArgT ) const> {
+        static const bool isValid = true;
+        using ArgType = typename std::remove_const<typename std::remove_reference<ArgT>::type>::type;
+        using ReturnType = ReturnT;
+    };
+
+    class TokenStream;
+
+    // Transport for raw args (copied from main args, or supplied via init list for testing)
+    class Args {
+        friend TokenStream;
+        std::string m_exeName;
+        std::vector<std::string> m_args;
+
+    public:
+        Args( int argc, char const* const* argv )
+            : m_exeName(argv[0]),
+              m_args(argv + 1, argv + argc) {}
+
+        Args( std::initializer_list<std::string> args )
+        :   m_exeName( *args.begin() ),
+            m_args( args.begin()+1, args.end() )
+        {}
+
+        auto exeName() const -> std::string {
+            return m_exeName;
+        }
+    };
+
+    // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string
+    // may encode an option + its argument if the : or = form is used
+    enum class TokenType {
+        Option, Argument
+    };
+    struct Token {
+        TokenType type;
+        std::string token;
+    };
+
+    inline auto isOptPrefix( char c ) -> bool {
+        return c == '-'
+#ifdef CATCH_PLATFORM_WINDOWS
+            || c == '/'
+#endif
+        ;
+    }
+
+    // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled
+    class TokenStream {
+        using Iterator = std::vector<std::string>::const_iterator;
+        Iterator it;
+        Iterator itEnd;
+        std::vector<Token> m_tokenBuffer;
+
+        void loadBuffer() {
+            m_tokenBuffer.resize( 0 );
+
+            // Skip any empty strings
+            while( it != itEnd && it->empty() )
+                ++it;
+
+            if( it != itEnd ) {
+                auto const &next = *it;
+                if( isOptPrefix( next[0] ) ) {
+                    auto delimiterPos = next.find_first_of( " :=" );
+                    if( delimiterPos != std::string::npos ) {
+                        m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } );
+                        m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } );
+                    } else {
+                        if( next[1] != '-' && next.size() > 2 ) {
+                            std::string opt = "- ";
+                            for( size_t i = 1; i < next.size(); ++i ) {
+                                opt[1] = next[i];
+                                m_tokenBuffer.push_back( { TokenType::Option, opt } );
+                            }
+                        } else {
+                            m_tokenBuffer.push_back( { TokenType::Option, next } );
+                        }
+                    }
+                } else {
+                    m_tokenBuffer.push_back( { TokenType::Argument, next } );
+                }
+            }
+        }
+
+    public:
+        explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {}
+
+        TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) {
+            loadBuffer();
+        }
+
+        explicit operator bool() const {
+            return !m_tokenBuffer.empty() || it != itEnd;
+        }
+
+        auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); }
+
+        auto operator*() const -> Token {
+            assert( !m_tokenBuffer.empty() );
+            return m_tokenBuffer.front();
+        }
+
+        auto operator->() const -> Token const * {
+            assert( !m_tokenBuffer.empty() );
+            return &m_tokenBuffer.front();
+        }
+
+        auto operator++() -> TokenStream & {
+            if( m_tokenBuffer.size() >= 2 ) {
+                m_tokenBuffer.erase( m_tokenBuffer.begin() );
+            } else {
+                if( it != itEnd )
+                    ++it;
+                loadBuffer();
+            }
+            return *this;
+        }
+    };
+
+    class ResultBase {
+    public:
+        enum Type {
+            Ok, LogicError, RuntimeError
+        };
+
+    protected:
+        ResultBase( Type type ) : m_type( type ) {}
+        virtual ~ResultBase() = default;
+
+        virtual void enforceOk() const = 0;
+
+        Type m_type;
+    };
+
+    template<typename T>
+    class ResultValueBase : public ResultBase {
+    public:
+        auto value() const -> T const & {
+            enforceOk();
+            return m_value;
+        }
+
+    protected:
+        ResultValueBase( Type type ) : ResultBase( type ) {}
+
+        ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) {
+            if( m_type == ResultBase::Ok )
+                new( &m_value ) T( other.m_value );
+        }
+
+        ResultValueBase( Type, T const &value ) : ResultBase( Ok ) {
+            new( &m_value ) T( value );
+        }
+
+        auto operator=( ResultValueBase const &other ) -> ResultValueBase & {
+            if( m_type == ResultBase::Ok )
+                m_value.~T();
+            ResultBase::operator=(other);
+            if( m_type == ResultBase::Ok )
+                new( &m_value ) T( other.m_value );
+            return *this;
+        }
+
+        ~ResultValueBase() override {
+            if( m_type == Ok )
+                m_value.~T();
+        }
+
+        union {
+            T m_value;
+        };
+    };
+
+    template<>
+    class ResultValueBase<void> : public ResultBase {
+    protected:
+        using ResultBase::ResultBase;
+    };
+
+    template<typename T = void>
+    class BasicResult : public ResultValueBase<T> {
+    public:
+        template<typename U>
+        explicit BasicResult( BasicResult<U> const &other )
+        :   ResultValueBase<T>( other.type() ),
+            m_errorMessage( other.errorMessage() )
+        {
+            assert( type() != ResultBase::Ok );
+        }
+
+        template<typename U>
+        static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; }
+        static auto ok() -> BasicResult { return { ResultBase::Ok }; }
+        static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; }
+        static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; }
+
+        explicit operator bool() const { return m_type == ResultBase::Ok; }
+        auto type() const -> ResultBase::Type { return m_type; }
+        auto errorMessage() const -> std::string { return m_errorMessage; }
+
+    protected:
+        void enforceOk() const override {
+
+            // Errors shouldn't reach this point, but if they do
+            // the actual error message will be in m_errorMessage
+            assert( m_type != ResultBase::LogicError );
+            assert( m_type != ResultBase::RuntimeError );
+            if( m_type != ResultBase::Ok )
+                std::abort();
+        }
+
+        std::string m_errorMessage; // Only populated if resultType is an error
+
+        BasicResult( ResultBase::Type type, std::string const &message )
+        :   ResultValueBase<T>(type),
+            m_errorMessage(message)
+        {
+            assert( m_type != ResultBase::Ok );
+        }
+
+        using ResultValueBase<T>::ResultValueBase;
+        using ResultBase::m_type;
+    };
+
+    enum class ParseResultType {
+        Matched, NoMatch, ShortCircuitAll, ShortCircuitSame
+    };
+
+    class ParseState {
+    public:
+
+        ParseState( ParseResultType type, TokenStream const &remainingTokens )
+        : m_type(type),
+          m_remainingTokens( remainingTokens )
+        {}
+
+        auto type() const -> ParseResultType { return m_type; }
+        auto remainingTokens() const -> TokenStream { return m_remainingTokens; }
+
+    private:
+        ParseResultType m_type;
+        TokenStream m_remainingTokens;
+    };
+
+    using Result = BasicResult<void>;
+    using ParserResult = BasicResult<ParseResultType>;
+    using InternalParseResult = BasicResult<ParseState>;
+
+    struct HelpColumns {
+        std::string left;
+        std::string right;
+    };
+
+    template<typename T>
+    inline auto convertInto( std::string const &source, T& target ) -> ParserResult {
+        std::stringstream ss;
+        ss << source;
+        ss >> target;
+        if( ss.fail() )
+            return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" );
+        else
+            return ParserResult::ok( ParseResultType::Matched );
+    }
+    inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult {
+        target = source;
+        return ParserResult::ok( ParseResultType::Matched );
+    }
+    inline auto convertInto( std::string const &source, bool &target ) -> ParserResult {
+        std::string srcLC = source;
+        std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast<char>( ::tolower(c) ); } );
+        if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on")
+            target = true;
+        else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off")
+            target = false;
+        else
+            return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" );
+        return ParserResult::ok( ParseResultType::Matched );
+    }
+#ifdef CLARA_CONFIG_OPTIONAL_TYPE
+    template<typename T>
+    inline auto convertInto( std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE<T>& target ) -> ParserResult {
+        T temp;
+        auto result = convertInto( source, temp );
+        if( result )
+            target = std::move(temp);
+        return result;
+    }
+#endif // CLARA_CONFIG_OPTIONAL_TYPE
+
+    struct NonCopyable {
+        NonCopyable() = default;
+        NonCopyable( NonCopyable const & ) = delete;
+        NonCopyable( NonCopyable && ) = delete;
+        NonCopyable &operator=( NonCopyable const & ) = delete;
+        NonCopyable &operator=( NonCopyable && ) = delete;
+    };
+
+    struct BoundRef : NonCopyable {
+        virtual ~BoundRef() = default;
+        virtual auto isContainer() const -> bool { return false; }
+        virtual auto isFlag() const -> bool { return false; }
+    };
+    struct BoundValueRefBase : BoundRef {
+        virtual auto setValue( std::string const &arg ) -> ParserResult = 0;
+    };
+    struct BoundFlagRefBase : BoundRef {
+        virtual auto setFlag( bool flag ) -> ParserResult = 0;
+        virtual auto isFlag() const -> bool { return true; }
+    };
+
+    template<typename T>
+    struct BoundValueRef : BoundValueRefBase {
+        T &m_ref;
+
+        explicit BoundValueRef( T &ref ) : m_ref( ref ) {}
+
+        auto setValue( std::string const &arg ) -> ParserResult override {
+            return convertInto( arg, m_ref );
+        }
+    };
+
+    template<typename T>
+    struct BoundValueRef<std::vector<T>> : BoundValueRefBase {
+        std::vector<T> &m_ref;
+
+        explicit BoundValueRef( std::vector<T> &ref ) : m_ref( ref ) {}
+
+        auto isContainer() const -> bool override { return true; }
+
+        auto setValue( std::string const &arg ) -> ParserResult override {
+            T temp;
+            auto result = convertInto( arg, temp );
+            if( result )
+                m_ref.push_back( temp );
+            return result;
+        }
+    };
+
+    struct BoundFlagRef : BoundFlagRefBase {
+        bool &m_ref;
+
+        explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {}
+
+        auto setFlag( bool flag ) -> ParserResult override {
+            m_ref = flag;
+            return ParserResult::ok( ParseResultType::Matched );
+        }
+    };
+
+    template<typename ReturnType>
+    struct LambdaInvoker {
+        static_assert( std::is_same<ReturnType, ParserResult>::value, "Lambda must return void or clara::ParserResult" );
+
+        template<typename L, typename ArgType>
+        static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult {
+            return lambda( arg );
+        }
+    };
+
+    template<>
+    struct LambdaInvoker<void> {
+        template<typename L, typename ArgType>
+        static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult {
+            lambda( arg );
+            return ParserResult::ok( ParseResultType::Matched );
+        }
+    };
+
+    template<typename ArgType, typename L>
+    inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult {
+        ArgType temp{};
+        auto result = convertInto( arg, temp );
+        return !result
+           ? result
+           : LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( lambda, temp );
+    }
+
+    template<typename L>
+    struct BoundLambda : BoundValueRefBase {
+        L m_lambda;
+
+        static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" );
+        explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {}
+
+        auto setValue( std::string const &arg ) -> ParserResult override {
+            return invokeLambda<typename UnaryLambdaTraits<L>::ArgType>( m_lambda, arg );
+        }
+    };
+
+    template<typename L>
+    struct BoundFlagLambda : BoundFlagRefBase {
+        L m_lambda;
+
+        static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" );
+        static_assert( std::is_same<typename UnaryLambdaTraits<L>::ArgType, bool>::value, "flags must be boolean" );
+
+        explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {}
+
+        auto setFlag( bool flag ) -> ParserResult override {
+            return LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( m_lambda, flag );
+        }
+    };
+
+    enum class Optionality { Optional, Required };
+
+    struct Parser;
+
+    class ParserBase {
+    public:
+        virtual ~ParserBase() = default;
+        virtual auto validate() const -> Result { return Result::ok(); }
+        virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult  = 0;
+        virtual auto cardinality() const -> size_t { return 1; }
+
+        auto parse( Args const &args ) const -> InternalParseResult {
+            return parse( args.exeName(), TokenStream( args ) );
+        }
+    };
+
+    template<typename DerivedT>
+    class ComposableParserImpl : public ParserBase {
+    public:
+        template<typename T>
+        auto operator|( T const &other ) const -> Parser;
+
+		template<typename T>
+        auto operator+( T const &other ) const -> Parser;
+    };
+
+    // Common code and state for Args and Opts
+    template<typename DerivedT>
+    class ParserRefImpl : public ComposableParserImpl<DerivedT> {
+    protected:
+        Optionality m_optionality = Optionality::Optional;
+        std::shared_ptr<BoundRef> m_ref;
+        std::string m_hint;
+        std::string m_description;
+
+        explicit ParserRefImpl( std::shared_ptr<BoundRef> const &ref ) : m_ref( ref ) {}
+
+    public:
+        template<typename T>
+        ParserRefImpl( T &ref, std::string const &hint )
+        :   m_ref( std::make_shared<BoundValueRef<T>>( ref ) ),
+            m_hint( hint )
+        {}
+
+        template<typename LambdaT>
+        ParserRefImpl( LambdaT const &ref, std::string const &hint )
+        :   m_ref( std::make_shared<BoundLambda<LambdaT>>( ref ) ),
+            m_hint(hint)
+        {}
+
+        auto operator()( std::string const &description ) -> DerivedT & {
+            m_description = description;
+            return static_cast<DerivedT &>( *this );
+        }
+
+        auto optional() -> DerivedT & {
+            m_optionality = Optionality::Optional;
+            return static_cast<DerivedT &>( *this );
+        };
+
+        auto required() -> DerivedT & {
+            m_optionality = Optionality::Required;
+            return static_cast<DerivedT &>( *this );
+        };
+
+        auto isOptional() const -> bool {
+            return m_optionality == Optionality::Optional;
+        }
+
+        auto cardinality() const -> size_t override {
+            if( m_ref->isContainer() )
+                return 0;
+            else
+                return 1;
+        }
+
+        auto hint() const -> std::string { return m_hint; }
+    };
+
+    class ExeName : public ComposableParserImpl<ExeName> {
+        std::shared_ptr<std::string> m_name;
+        std::shared_ptr<BoundValueRefBase> m_ref;
+
+        template<typename LambdaT>
+        static auto makeRef(LambdaT const &lambda) -> std::shared_ptr<BoundValueRefBase> {
+            return std::make_shared<BoundLambda<LambdaT>>( lambda) ;
+        }
+
+    public:
+        ExeName() : m_name( std::make_shared<std::string>( "<executable>" ) ) {}
+
+        explicit ExeName( std::string &ref ) : ExeName() {
+            m_ref = std::make_shared<BoundValueRef<std::string>>( ref );
+        }
+
+        template<typename LambdaT>
+        explicit ExeName( LambdaT const& lambda ) : ExeName() {
+            m_ref = std::make_shared<BoundLambda<LambdaT>>( lambda );
+        }
+
+        // The exe name is not parsed out of the normal tokens, but is handled specially
+        auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override {
+            return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) );
+        }
+
+        auto name() const -> std::string { return *m_name; }
+        auto set( std::string const& newName ) -> ParserResult {
+
+            auto lastSlash = newName.find_last_of( "\\/" );
+            auto filename = ( lastSlash == std::string::npos )
+                    ? newName
+                    : newName.substr( lastSlash+1 );
+
+            *m_name = filename;
+            if( m_ref )
+                return m_ref->setValue( filename );
+            else
+                return ParserResult::ok( ParseResultType::Matched );
+        }
+    };
+
+    class Arg : public ParserRefImpl<Arg> {
+    public:
+        using ParserRefImpl::ParserRefImpl;
+
+        auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override {
+            auto validationResult = validate();
+            if( !validationResult )
+                return InternalParseResult( validationResult );
+
+            auto remainingTokens = tokens;
+            auto const &token = *remainingTokens;
+            if( token.type != TokenType::Argument )
+                return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) );
+
+            assert( !m_ref->isFlag() );
+            auto valueRef = static_cast<detail::BoundValueRefBase*>( m_ref.get() );
+
+            auto result = valueRef->setValue( remainingTokens->token );
+            if( !result )
+                return InternalParseResult( result );
+            else
+                return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) );
+        }
+    };
+
+    inline auto normaliseOpt( std::string const &optName ) -> std::string {
+#ifdef CATCH_PLATFORM_WINDOWS
+        if( optName[0] == '/' )
+            return "-" + optName.substr( 1 );
+        else
+#endif
+            return optName;
+    }
+
+    class Opt : public ParserRefImpl<Opt> {
+    protected:
+        std::vector<std::string> m_optNames;
+
+    public:
+        template<typename LambdaT>
+        explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared<BoundFlagLambda<LambdaT>>( ref ) ) {}
+
+        explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared<BoundFlagRef>( ref ) ) {}
+
+        template<typename LambdaT>
+        Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {}
+
+        template<typename T>
+        Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {}
+
+        auto operator[]( std::string const &optName ) -> Opt & {
+            m_optNames.push_back( optName );
+            return *this;
+        }
+
+        auto getHelpColumns() const -> std::vector<HelpColumns> {
+            std::ostringstream oss;
+            bool first = true;
+            for( auto const &opt : m_optNames ) {
+                if (first)
+                    first = false;
+                else
+                    oss << ", ";
+                oss << opt;
+            }
+            if( !m_hint.empty() )
+                oss << " <" << m_hint << ">";
+            return { { oss.str(), m_description } };
+        }
+
+        auto isMatch( std::string const &optToken ) const -> bool {
+            auto normalisedToken = normaliseOpt( optToken );
+            for( auto const &name : m_optNames ) {
+                if( normaliseOpt( name ) == normalisedToken )
+                    return true;
+            }
+            return false;
+        }
+
+        using ParserBase::parse;
+
+        auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override {
+            auto validationResult = validate();
+            if( !validationResult )
+                return InternalParseResult( validationResult );
+
+            auto remainingTokens = tokens;
+            if( remainingTokens && remainingTokens->type == TokenType::Option ) {
+                auto const &token = *remainingTokens;
+                if( isMatch(token.token ) ) {
+                    if( m_ref->isFlag() ) {
+                        auto flagRef = static_cast<detail::BoundFlagRefBase*>( m_ref.get() );
+                        auto result = flagRef->setFlag( true );
+                        if( !result )
+                            return InternalParseResult( result );
+                        if( result.value() == ParseResultType::ShortCircuitAll )
+                            return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) );
+                    } else {
+                        auto valueRef = static_cast<detail::BoundValueRefBase*>( m_ref.get() );
+                        ++remainingTokens;
+                        if( !remainingTokens )
+                            return InternalParseResult::runtimeError( "Expected argument following " + token.token );
+                        auto const &argToken = *remainingTokens;
+                        if( argToken.type != TokenType::Argument )
+                            return InternalParseResult::runtimeError( "Expected argument following " + token.token );
+                        auto result = valueRef->setValue( argToken.token );
+                        if( !result )
+                            return InternalParseResult( result );
+                        if( result.value() == ParseResultType::ShortCircuitAll )
+                            return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) );
+                    }
+                    return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) );
+                }
+            }
+            return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) );
+        }
+
+        auto validate() const -> Result override {
+            if( m_optNames.empty() )
+                return Result::logicError( "No options supplied to Opt" );
+            for( auto const &name : m_optNames ) {
+                if( name.empty() )
+                    return Result::logicError( "Option name cannot be empty" );
+#ifdef CATCH_PLATFORM_WINDOWS
+                if( name[0] != '-' && name[0] != '/' )
+                    return Result::logicError( "Option name must begin with '-' or '/'" );
+#else
+                if( name[0] != '-' )
+                    return Result::logicError( "Option name must begin with '-'" );
+#endif
+            }
+            return ParserRefImpl::validate();
+        }
+    };
+
+    struct Help : Opt {
+        Help( bool &showHelpFlag )
+        :   Opt([&]( bool flag ) {
+                showHelpFlag = flag;
+                return ParserResult::ok( ParseResultType::ShortCircuitAll );
+            })
+        {
+            static_cast<Opt &>( *this )
+                    ("display usage information")
+                    ["-?"]["-h"]["--help"]
+                    .optional();
+        }
+    };
+
+    struct Parser : ParserBase {
+
+        mutable ExeName m_exeName;
+        std::vector<Opt> m_options;
+        std::vector<Arg> m_args;
+
+        auto operator|=( ExeName const &exeName ) -> Parser & {
+            m_exeName = exeName;
+            return *this;
+        }
+
+        auto operator|=( Arg const &arg ) -> Parser & {
+            m_args.push_back(arg);
+            return *this;
+        }
+
+        auto operator|=( Opt const &opt ) -> Parser & {
+            m_options.push_back(opt);
+            return *this;
+        }
+
+        auto operator|=( Parser const &other ) -> Parser & {
+            m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end());
+            m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end());
+            return *this;
+        }
+
+        template<typename T>
+        auto operator|( T const &other ) const -> Parser {
+            return Parser( *this ) |= other;
+        }
+
+        // Forward deprecated interface with '+' instead of '|'
+        template<typename T>
+        auto operator+=( T const &other ) -> Parser & { return operator|=( other ); }
+        template<typename T>
+        auto operator+( T const &other ) const -> Parser { return operator|( other ); }
+
+        auto getHelpColumns() const -> std::vector<HelpColumns> {
+            std::vector<HelpColumns> cols;
+            for (auto const &o : m_options) {
+                auto childCols = o.getHelpColumns();
+                cols.insert( cols.end(), childCols.begin(), childCols.end() );
+            }
+            return cols;
+        }
+
+        void writeToStream( std::ostream &os ) const {
+            if (!m_exeName.name().empty()) {
+                os << "usage:\n" << "  " << m_exeName.name() << " ";
+                bool required = true, first = true;
+                for( auto const &arg : m_args ) {
+                    if (first)
+                        first = false;
+                    else
+                        os << " ";
+                    if( arg.isOptional() && required ) {
+                        os << "[";
+                        required = false;
+                    }
+                    os << "<" << arg.hint() << ">";
+                    if( arg.cardinality() == 0 )
+                        os << " ... ";
+                }
+                if( !required )
+                    os << "]";
+                if( !m_options.empty() )
+                    os << " options";
+                os << "\n\nwhere options are:" << std::endl;
+            }
+
+            auto rows = getHelpColumns();
+            size_t consoleWidth = CATCH_CLARA_CONFIG_CONSOLE_WIDTH;
+            size_t optWidth = 0;
+            for( auto const &cols : rows )
+                optWidth = (std::max)(optWidth, cols.left.size() + 2);
+
+            optWidth = (std::min)(optWidth, consoleWidth/2);
+
+            for( auto const &cols : rows ) {
+                auto row =
+                        TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) +
+                        TextFlow::Spacer(4) +
+                        TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth );
+                os << row << std::endl;
+            }
+        }
+
+        friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& {
+            parser.writeToStream( os );
+            return os;
+        }
+
+        auto validate() const -> Result override {
+            for( auto const &opt : m_options ) {
+                auto result = opt.validate();
+                if( !result )
+                    return result;
+            }
+            for( auto const &arg : m_args ) {
+                auto result = arg.validate();
+                if( !result )
+                    return result;
+            }
+            return Result::ok();
+        }
+
+        using ParserBase::parse;
+
+        auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override {
+
+            struct ParserInfo {
+                ParserBase const* parser = nullptr;
+                size_t count = 0;
+            };
+            const size_t totalParsers = m_options.size() + m_args.size();
+            assert( totalParsers < 512 );
+            // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do
+            ParserInfo parseInfos[512];
+
+            {
+                size_t i = 0;
+                for (auto const &opt : m_options) parseInfos[i++].parser = &opt;
+                for (auto const &arg : m_args) parseInfos[i++].parser = &arg;
+            }
+
+            m_exeName.set( exeName );
+
+            auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) );
+            while( result.value().remainingTokens() ) {
+                bool tokenParsed = false;
+
+                for( size_t i = 0; i < totalParsers; ++i ) {
+                    auto&  parseInfo = parseInfos[i];
+                    if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) {
+                        result = parseInfo.parser->parse(exeName, result.value().remainingTokens());
+                        if (!result)
+                            return result;
+                        if (result.value().type() != ParseResultType::NoMatch) {
+                            tokenParsed = true;
+                            ++parseInfo.count;
+                            break;
+                        }
+                    }
+                }
+
+                if( result.value().type() == ParseResultType::ShortCircuitAll )
+                    return result;
+                if( !tokenParsed )
+                    return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token );
+            }
+            // !TBD Check missing required options
+            return result;
+        }
+    };
+
+    template<typename DerivedT>
+    template<typename T>
+    auto ComposableParserImpl<DerivedT>::operator|( T const &other ) const -> Parser {
+        return Parser() | static_cast<DerivedT const &>( *this ) | other;
+    }
+} // namespace detail
+
+// A Combined parser
+using detail::Parser;
+
+// A parser for options
+using detail::Opt;
+
+// A parser for arguments
+using detail::Arg;
+
+// Wrapper for argc, argv from main()
+using detail::Args;
+
+// Specifies the name of the executable
+using detail::ExeName;
+
+// Convenience wrapper for option parser that specifies the help option
+using detail::Help;
+
+// enum of result types from a parse
+using detail::ParseResultType;
+
+// Result type for parser operation
+using detail::ParserResult;
+
+}} // namespace Catch::clara
+
+// end clara.hpp
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+// Restore Clara's value for console width, if present
+#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH
+#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH
+#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH
+#endif
+
+// end catch_clara.h
+namespace Catch {
+
+    clara::Parser makeCommandLineParser( ConfigData& config );
+
+} // end namespace Catch
+
+// end catch_commandline.h
+#include <fstream>
+#include <ctime>
+
+namespace Catch {
+
+    clara::Parser makeCommandLineParser( ConfigData& config ) {
+
+        using namespace clara;
+
+        auto const setWarning = [&]( std::string const& warning ) {
+                auto warningSet = [&]() {
+                    if( warning == "NoAssertions" )
+                        return WarnAbout::NoAssertions;
+
+                    if ( warning == "NoTests" )
+                        return WarnAbout::NoTests;
+
+                    return WarnAbout::Nothing;
+                }();
+
+                if (warningSet == WarnAbout::Nothing)
+                    return ParserResult::runtimeError( "Unrecognised warning: '" + warning + "'" );
+                config.warnings = static_cast<WarnAbout::What>( config.warnings | warningSet );
+                return ParserResult::ok( ParseResultType::Matched );
+            };
+        auto const loadTestNamesFromFile = [&]( std::string const& filename ) {
+                std::ifstream f( filename.c_str() );
+                if( !f.is_open() )
+                    return ParserResult::runtimeError( "Unable to load input file: '" + filename + "'" );
+
+                std::string line;
+                while( std::getline( f, line ) ) {
+                    line = trim(line);
+                    if( !line.empty() && !startsWith( line, '#' ) ) {
+                        if( !startsWith( line, '"' ) )
+                            line = '"' + line + '"';
+                        config.testsOrTags.push_back( line + ',' );
+                    }
+                }
+                return ParserResult::ok( ParseResultType::Matched );
+            };
+        auto const setTestOrder = [&]( std::string const& order ) {
+                if( startsWith( "declared", order ) )
+                    config.runOrder = RunTests::InDeclarationOrder;
+                else if( startsWith( "lexical", order ) )
+                    config.runOrder = RunTests::InLexicographicalOrder;
+                else if( startsWith( "random", order ) )
+                    config.runOrder = RunTests::InRandomOrder;
+                else
+                    return clara::ParserResult::runtimeError( "Unrecognised ordering: '" + order + "'" );
+                return ParserResult::ok( ParseResultType::Matched );
+            };
+        auto const setRngSeed = [&]( std::string const& seed ) {
+                if( seed != "time" )
+                    return clara::detail::convertInto( seed, config.rngSeed );
+                config.rngSeed = static_cast<unsigned int>( std::time(nullptr) );
+                return ParserResult::ok( ParseResultType::Matched );
+            };
+        auto const setColourUsage = [&]( std::string const& useColour ) {
+                    auto mode = toLower( useColour );
+
+                    if( mode == "yes" )
+                        config.useColour = UseColour::Yes;
+                    else if( mode == "no" )
+                        config.useColour = UseColour::No;
+                    else if( mode == "auto" )
+                        config.useColour = UseColour::Auto;
+                    else
+                        return ParserResult::runtimeError( "colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised" );
+                return ParserResult::ok( ParseResultType::Matched );
+            };
+        auto const setWaitForKeypress = [&]( std::string const& keypress ) {
+                auto keypressLc = toLower( keypress );
+                if( keypressLc == "start" )
+                    config.waitForKeypress = WaitForKeypress::BeforeStart;
+                else if( keypressLc == "exit" )
+                    config.waitForKeypress = WaitForKeypress::BeforeExit;
+                else if( keypressLc == "both" )
+                    config.waitForKeypress = WaitForKeypress::BeforeStartAndExit;
+                else
+                    return ParserResult::runtimeError( "keypress argument must be one of: start, exit or both. '" + keypress + "' not recognised" );
+            return ParserResult::ok( ParseResultType::Matched );
+            };
+        auto const setVerbosity = [&]( std::string const& verbosity ) {
+            auto lcVerbosity = toLower( verbosity );
+            if( lcVerbosity == "quiet" )
+                config.verbosity = Verbosity::Quiet;
+            else if( lcVerbosity == "normal" )
+                config.verbosity = Verbosity::Normal;
+            else if( lcVerbosity == "high" )
+                config.verbosity = Verbosity::High;
+            else
+                return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + "'" );
+            return ParserResult::ok( ParseResultType::Matched );
+        };
+        auto const setReporter = [&]( std::string const& reporter ) {
+            IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories();
+
+            auto lcReporter = toLower( reporter );
+            auto result = factories.find( lcReporter );
+
+            if( factories.end() != result )
+                config.reporterName = lcReporter;
+            else
+                return ParserResult::runtimeError( "Unrecognized reporter, '" + reporter + "'. Check available with --list-reporters" );
+            return ParserResult::ok( ParseResultType::Matched );
+        };
+
+        auto cli
+            = ExeName( config.processName )
+            | Help( config.showHelp )
+            | Opt( config.listTests )
+                ["-l"]["--list-tests"]
+                ( "list all/matching test cases" )
+            | Opt( config.listTags )
+                ["-t"]["--list-tags"]
+                ( "list all/matching tags" )
+            | Opt( config.showSuccessfulTests )
+                ["-s"]["--success"]
+                ( "include successful tests in output" )
+            | Opt( config.shouldDebugBreak )
+                ["-b"]["--break"]
+                ( "break into debugger on failure" )
+            | Opt( config.noThrow )
+                ["-e"]["--nothrow"]
+                ( "skip exception tests" )
+            | Opt( config.showInvisibles )
+                ["-i"]["--invisibles"]
+                ( "show invisibles (tabs, newlines)" )
+            | Opt( config.outputFilename, "filename" )
+                ["-o"]["--out"]
+                ( "output filename" )
+            | Opt( setReporter, "name" )
+                ["-r"]["--reporter"]
+                ( "reporter to use (defaults to console)" )
+            | Opt( config.name, "name" )
+                ["-n"]["--name"]
+                ( "suite name" )
+            | Opt( [&]( bool ){ config.abortAfter = 1; } )
+                ["-a"]["--abort"]
+                ( "abort at first failure" )
+            | Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" )
+                ["-x"]["--abortx"]
+                ( "abort after x failures" )
+            | Opt( setWarning, "warning name" )
+                ["-w"]["--warn"]
+                ( "enable warnings" )
+            | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" )
+                ["-d"]["--durations"]
+                ( "show test durations" )
+            | Opt( loadTestNamesFromFile, "filename" )
+                ["-f"]["--input-file"]
+                ( "load test names to run from a file" )
+            | Opt( config.filenamesAsTags )
+                ["-#"]["--filenames-as-tags"]
+                ( "adds a tag for the filename" )
+            | Opt( config.sectionsToRun, "section name" )
+                ["-c"]["--section"]
+                ( "specify section to run" )
+            | Opt( setVerbosity, "quiet|normal|high" )
+                ["-v"]["--verbosity"]
+                ( "set output verbosity" )
+            | Opt( config.listTestNamesOnly )
+                ["--list-test-names-only"]
+                ( "list all/matching test cases names only" )
+            | Opt( config.listReporters )
+                ["--list-reporters"]
+                ( "list all reporters" )
+            | Opt( setTestOrder, "decl|lex|rand" )
+                ["--order"]
+                ( "test case order (defaults to decl)" )
+            | Opt( setRngSeed, "'time'|number" )
+                ["--rng-seed"]
+                ( "set a specific seed for random numbers" )
+            | Opt( setColourUsage, "yes|no" )
+                ["--use-colour"]
+                ( "should output be colourised" )
+            | Opt( config.libIdentify )
+                ["--libidentify"]
+                ( "report name and version according to libidentify standard" )
+            | Opt( setWaitForKeypress, "start|exit|both" )
+                ["--wait-for-keypress"]
+                ( "waits for a keypress before exiting" )
+            | Opt( config.benchmarkResolutionMultiple, "multiplier" )
+                ["--benchmark-resolution-multiple"]
+                ( "multiple of clock resolution to run benchmarks" )
+
+            | Arg( config.testsOrTags, "test name|pattern|tags" )
+                ( "which test or tests to use" );
+
+        return cli;
+    }
+
+} // end namespace Catch
+// end catch_commandline.cpp
+// start catch_common.cpp
+
+#include <cstring>
+#include <ostream>
+
+namespace Catch {
+
+    bool SourceLineInfo::empty() const noexcept {
+        return file[0] == '\0';
+    }
+    bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept {
+        return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0);
+    }
+    bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept {
+        // We can assume that the same file will usually have the same pointer.
+        // Thus, if the pointers are the same, there is no point in calling the strcmp
+        return line < other.line || ( line == other.line && file != other.file && (std::strcmp(file, other.file) < 0));
+    }
+
+    std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) {
+#ifndef __GNUG__
+        os << info.file << '(' << info.line << ')';
+#else
+        os << info.file << ':' << info.line;
+#endif
+        return os;
+    }
+
+    std::string StreamEndStop::operator+() const {
+        return std::string();
+    }
+
+    NonCopyable::NonCopyable() = default;
+    NonCopyable::~NonCopyable() = default;
+
+}
+// end catch_common.cpp
+// start catch_config.cpp
+
+namespace Catch {
+
+    Config::Config( ConfigData const& data )
+    :   m_data( data ),
+        m_stream( openStream() )
+    {
+        TestSpecParser parser(ITagAliasRegistry::get());
+        if (data.testsOrTags.empty()) {
+            parser.parse("~[.]"); // All not hidden tests
+        }
+        else {
+            m_hasTestFilters = true;
+            for( auto const& testOrTags : data.testsOrTags )
+                parser.parse( testOrTags );
+        }
+        m_testSpec = parser.testSpec();
+    }
+
+    std::string const& Config::getFilename() const {
+        return m_data.outputFilename ;
+    }
+
+    bool Config::listTests() const          { return m_data.listTests; }
+    bool Config::listTestNamesOnly() const  { return m_data.listTestNamesOnly; }
+    bool Config::listTags() const           { return m_data.listTags; }
+    bool Config::listReporters() const      { return m_data.listReporters; }
+
+    std::string Config::getProcessName() const { return m_data.processName; }
+    std::string const& Config::getReporterName() const { return m_data.reporterName; }
+
+    std::vector<std::string> const& Config::getTestsOrTags() const { return m_data.testsOrTags; }
+    std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; }
+
+    TestSpec const& Config::testSpec() const { return m_testSpec; }
+    bool Config::hasTestFilters() const { return m_hasTestFilters; }
+
+    bool Config::showHelp() const { return m_data.showHelp; }
+
+    // IConfig interface
+    bool Config::allowThrows() const                   { return !m_data.noThrow; }
+    std::ostream& Config::stream() const               { return m_stream->stream(); }
+    std::string Config::name() const                   { return m_data.name.empty() ? m_data.processName : m_data.name; }
+    bool Config::includeSuccessfulResults() const      { return m_data.showSuccessfulTests; }
+    bool Config::warnAboutMissingAssertions() const    { return !!(m_data.warnings & WarnAbout::NoAssertions); }
+    bool Config::warnAboutNoTests() const              { return !!(m_data.warnings & WarnAbout::NoTests); }
+    ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; }
+    RunTests::InWhatOrder Config::runOrder() const     { return m_data.runOrder; }
+    unsigned int Config::rngSeed() const               { return m_data.rngSeed; }
+    int Config::benchmarkResolutionMultiple() const    { return m_data.benchmarkResolutionMultiple; }
+    UseColour::YesOrNo Config::useColour() const       { return m_data.useColour; }
+    bool Config::shouldDebugBreak() const              { return m_data.shouldDebugBreak; }
+    int Config::abortAfter() const                     { return m_data.abortAfter; }
+    bool Config::showInvisibles() const                { return m_data.showInvisibles; }
+    Verbosity Config::verbosity() const                { return m_data.verbosity; }
+
+    IStream const* Config::openStream() {
+        return Catch::makeStream(m_data.outputFilename);
+    }
+
+} // end namespace Catch
+// end catch_config.cpp
+// start catch_console_colour.cpp
+
+#if defined(__clang__)
+#    pragma clang diagnostic push
+#    pragma clang diagnostic ignored "-Wexit-time-destructors"
+#endif
+
+// start catch_errno_guard.h
+
+namespace Catch {
+
+    class ErrnoGuard {
+    public:
+        ErrnoGuard();
+        ~ErrnoGuard();
+    private:
+        int m_oldErrno;
+    };
+
+}
+
+// end catch_errno_guard.h
+#include <sstream>
+
+namespace Catch {
+    namespace {
+
+        struct IColourImpl {
+            virtual ~IColourImpl() = default;
+            virtual void use( Colour::Code _colourCode ) = 0;
+        };
+
+        struct NoColourImpl : IColourImpl {
+            void use( Colour::Code ) {}
+
+            static IColourImpl* instance() {
+                static NoColourImpl s_instance;
+                return &s_instance;
+            }
+        };
+
+    } // anon namespace
+} // namespace Catch
+
+#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI )
+#   ifdef CATCH_PLATFORM_WINDOWS
+#       define CATCH_CONFIG_COLOUR_WINDOWS
+#   else
+#       define CATCH_CONFIG_COLOUR_ANSI
+#   endif
+#endif
+
+#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) /////////////////////////////////////////
+
+namespace Catch {
+namespace {
+
+    class Win32ColourImpl : public IColourImpl {
+    public:
+        Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) )
+        {
+            CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
+            GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo );
+            originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY );
+            originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY );
+        }
+
+        virtual void use( Colour::Code _colourCode ) override {
+            switch( _colourCode ) {
+                case Colour::None:      return setTextAttribute( originalForegroundAttributes );
+                case Colour::White:     return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );
+                case Colour::Red:       return setTextAttribute( FOREGROUND_RED );
+                case Colour::Green:     return setTextAttribute( FOREGROUND_GREEN );
+                case Colour::Blue:      return setTextAttribute( FOREGROUND_BLUE );
+                case Colour::Cyan:      return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN );
+                case Colour::Yellow:    return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN );
+                case Colour::Grey:      return setTextAttribute( 0 );
+
+                case Colour::LightGrey:     return setTextAttribute( FOREGROUND_INTENSITY );
+                case Colour::BrightRed:     return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED );
+                case Colour::BrightGreen:   return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN );
+                case Colour::BrightWhite:   return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );
+                case Colour::BrightYellow:  return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN );
+
+                case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" );
+
+                default:
+                    CATCH_ERROR( "Unknown colour requested" );
+            }
+        }
+
+    private:
+        void setTextAttribute( WORD _textAttribute ) {
+            SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes );
+        }
+        HANDLE stdoutHandle;
+        WORD originalForegroundAttributes;
+        WORD originalBackgroundAttributes;
+    };
+
+    IColourImpl* platformColourInstance() {
+        static Win32ColourImpl s_instance;
+
+        IConfigPtr config = getCurrentContext().getConfig();
+        UseColour::YesOrNo colourMode = config
+            ? config->useColour()
+            : UseColour::Auto;
+        if( colourMode == UseColour::Auto )
+            colourMode = UseColour::Yes;
+        return colourMode == UseColour::Yes
+            ? &s_instance
+            : NoColourImpl::instance();
+    }
+
+} // end anon namespace
+} // end namespace Catch
+
+#elif defined( CATCH_CONFIG_COLOUR_ANSI ) //////////////////////////////////////
+
+#include <unistd.h>
+
+namespace Catch {
+namespace {
+
+    // use POSIX/ ANSI console terminal codes
+    // Thanks to Adam Strzelecki for original contribution
+    // (http://github.com/nanoant)
+    // https://github.com/philsquared/Catch/pull/131
+    class PosixColourImpl : public IColourImpl {
+    public:
+        virtual void use( Colour::Code _colourCode ) override {
+            switch( _colourCode ) {
+                case Colour::None:
+                case Colour::White:     return setColour( "[0m" );
+                case Colour::Red:       return setColour( "[0;31m" );
+                case Colour::Green:     return setColour( "[0;32m" );
+                case Colour::Blue:      return setColour( "[0;34m" );
+                case Colour::Cyan:      return setColour( "[0;36m" );
+                case Colour::Yellow:    return setColour( "[0;33m" );
+                case Colour::Grey:      return setColour( "[1;30m" );
+
+                case Colour::LightGrey:     return setColour( "[0;37m" );
+                case Colour::BrightRed:     return setColour( "[1;31m" );
+                case Colour::BrightGreen:   return setColour( "[1;32m" );
+                case Colour::BrightWhite:   return setColour( "[1;37m" );
+                case Colour::BrightYellow:  return setColour( "[1;33m" );
+
+                case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" );
+                default: CATCH_INTERNAL_ERROR( "Unknown colour requested" );
+            }
+        }
+        static IColourImpl* instance() {
+            static PosixColourImpl s_instance;
+            return &s_instance;
+        }
+
+    private:
+        void setColour( const char* _escapeCode ) {
+            Catch::cout() << '\033' << _escapeCode;
+        }
+    };
+
+    bool useColourOnPlatform() {
+        return
+#ifdef CATCH_PLATFORM_MAC
+            !isDebuggerActive() &&
+#endif
+#if !(defined(__DJGPP__) && defined(__STRICT_ANSI__))
+            isatty(STDOUT_FILENO)
+#else
+            false
+#endif
+            ;
+    }
+    IColourImpl* platformColourInstance() {
+        ErrnoGuard guard;
+        IConfigPtr config = getCurrentContext().getConfig();
+        UseColour::YesOrNo colourMode = config
+            ? config->useColour()
+            : UseColour::Auto;
+        if( colourMode == UseColour::Auto )
+            colourMode = useColourOnPlatform()
+                ? UseColour::Yes
+                : UseColour::No;
+        return colourMode == UseColour::Yes
+            ? PosixColourImpl::instance()
+            : NoColourImpl::instance();
+    }
+
+} // end anon namespace
+} // end namespace Catch
+
+#else  // not Windows or ANSI ///////////////////////////////////////////////
+
+namespace Catch {
+
+    static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); }
+
+} // end namespace Catch
+
+#endif // Windows/ ANSI/ None
+
+namespace Catch {
+
+    Colour::Colour( Code _colourCode ) { use( _colourCode ); }
+    Colour::Colour( Colour&& rhs ) noexcept {
+        m_moved = rhs.m_moved;
+        rhs.m_moved = true;
+    }
+    Colour& Colour::operator=( Colour&& rhs ) noexcept {
+        m_moved = rhs.m_moved;
+        rhs.m_moved  = true;
+        return *this;
+    }
+
+    Colour::~Colour(){ if( !m_moved ) use( None ); }
+
+    void Colour::use( Code _colourCode ) {
+        static IColourImpl* impl = platformColourInstance();
+        impl->use( _colourCode );
+    }
+
+    std::ostream& operator << ( std::ostream& os, Colour const& ) {
+        return os;
+    }
+
+} // end namespace Catch
+
+#if defined(__clang__)
+#    pragma clang diagnostic pop
+#endif
+
+// end catch_console_colour.cpp
+// start catch_context.cpp
+
+namespace Catch {
+
+    class Context : public IMutableContext, NonCopyable {
+
+    public: // IContext
+        virtual IResultCapture* getResultCapture() override {
+            return m_resultCapture;
+        }
+        virtual IRunner* getRunner() override {
+            return m_runner;
+        }
+
+        virtual IConfigPtr const& getConfig() const override {
+            return m_config;
+        }
+
+        virtual ~Context() override;
+
+    public: // IMutableContext
+        virtual void setResultCapture( IResultCapture* resultCapture ) override {
+            m_resultCapture = resultCapture;
+        }
+        virtual void setRunner( IRunner* runner ) override {
+            m_runner = runner;
+        }
+        virtual void setConfig( IConfigPtr const& config ) override {
+            m_config = config;
+        }
+
+        friend IMutableContext& getCurrentMutableContext();
+
+    private:
+        IConfigPtr m_config;
+        IRunner* m_runner = nullptr;
+        IResultCapture* m_resultCapture = nullptr;
+    };
+
+    IMutableContext *IMutableContext::currentContext = nullptr;
+
+    void IMutableContext::createContext()
+    {
+        currentContext = new Context();
+    }
+
+    void cleanUpContext() {
+        delete IMutableContext::currentContext;
+        IMutableContext::currentContext = nullptr;
+    }
+    IContext::~IContext() = default;
+    IMutableContext::~IMutableContext() = default;
+    Context::~Context() = default;
+}
+// end catch_context.cpp
+// start catch_debug_console.cpp
+
+// start catch_debug_console.h
+
+#include <string>
+
+namespace Catch {
+    void writeToDebugConsole( std::string const& text );
+}
+
+// end catch_debug_console.h
+#ifdef CATCH_PLATFORM_WINDOWS
+
+    namespace Catch {
+        void writeToDebugConsole( std::string const& text ) {
+            ::OutputDebugStringA( text.c_str() );
+        }
+    }
+
+#else
+
+    namespace Catch {
+        void writeToDebugConsole( std::string const& text ) {
+            // !TBD: Need a version for Mac/ XCode and other IDEs
+            Catch::cout() << text;
+        }
+    }
+
+#endif // Platform
+// end catch_debug_console.cpp
+// start catch_debugger.cpp
+
+#ifdef CATCH_PLATFORM_MAC
+
+#  include <assert.h>
+#  include <stdbool.h>
+#  include <sys/types.h>
+#  include <unistd.h>
+#  include <sys/sysctl.h>
+#  include <cstddef>
+#  include <ostream>
+
+namespace Catch {
+
+        // The following function is taken directly from the following technical note:
+        // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html
+
+        // Returns true if the current process is being debugged (either
+        // running under the debugger or has a debugger attached post facto).
+        bool isDebuggerActive(){
+
+            int                 mib[4];
+            struct kinfo_proc   info;
+            std::size_t         size;
+
+            // Initialize the flags so that, if sysctl fails for some bizarre
+            // reason, we get a predictable result.
+
+            info.kp_proc.p_flag = 0;
+
+            // Initialize mib, which tells sysctl the info we want, in this case
+            // we're looking for information about a specific process ID.
+
+            mib[0] = CTL_KERN;
+            mib[1] = KERN_PROC;
+            mib[2] = KERN_PROC_PID;
+            mib[3] = getpid();
+
+            // Call sysctl.
+
+            size = sizeof(info);
+            if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0 ) {
+                Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl;
+                return false;
+            }
+
+            // We're being debugged if the P_TRACED flag is set.
+
+            return ( (info.kp_proc.p_flag & P_TRACED) != 0 );
+        }
+    } // namespace Catch
+
+#elif defined(CATCH_PLATFORM_LINUX)
+    #include <fstream>
+    #include <string>
+
+    namespace Catch{
+        // The standard POSIX way of detecting a debugger is to attempt to
+        // ptrace() the process, but this needs to be done from a child and not
+        // this process itself to still allow attaching to this process later
+        // if wanted, so is rather heavy. Under Linux we have the PID of the
+        // "debugger" (which doesn't need to be gdb, of course, it could also
+        // be strace, for example) in /proc/$PID/status, so just get it from
+        // there instead.
+        bool isDebuggerActive(){
+            // Libstdc++ has a bug, where std::ifstream sets errno to 0
+            // This way our users can properly assert over errno values
+            ErrnoGuard guard;
+            std::ifstream in("/proc/self/status");
+            for( std::string line; std::getline(in, line); ) {
+                static const int PREFIX_LEN = 11;
+                if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) {
+                    // We're traced if the PID is not 0 and no other PID starts
+                    // with 0 digit, so it's enough to check for just a single
+                    // character.
+                    return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0';
+                }
+            }
+
+            return false;
+        }
+    } // namespace Catch
+#elif defined(_MSC_VER)
+    extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent();
+    namespace Catch {
+        bool isDebuggerActive() {
+            return IsDebuggerPresent() != 0;
+        }
+    }
+#elif defined(__MINGW32__)
+    extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent();
+    namespace Catch {
+        bool isDebuggerActive() {
+            return IsDebuggerPresent() != 0;
+        }
+    }
+#else
+    namespace Catch {
+       bool isDebuggerActive() { return false; }
+    }
+#endif // Platform
+// end catch_debugger.cpp
+// start catch_decomposer.cpp
+
+namespace Catch {
+
+    ITransientExpression::~ITransientExpression() = default;
+
+    void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ) {
+        if( lhs.size() + rhs.size() < 40 &&
+                lhs.find('\n') == std::string::npos &&
+                rhs.find('\n') == std::string::npos )
+            os << lhs << " " << op << " " << rhs;
+        else
+            os << lhs << "\n" << op << "\n" << rhs;
+    }
+}
+// end catch_decomposer.cpp
+// start catch_enforce.cpp
+
+namespace Catch {
+#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER)
+    [[noreturn]]
+    void throw_exception(std::exception const& e) {
+        Catch::cerr() << "Catch will terminate because it needed to throw an exception.\n"
+                      << "The message was: " << e.what() << '\n';
+        std::terminate();
+    }
+#endif
+} // namespace Catch;
+// end catch_enforce.cpp
+// start catch_errno_guard.cpp
+
+#include <cerrno>
+
+namespace Catch {
+        ErrnoGuard::ErrnoGuard():m_oldErrno(errno){}
+        ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; }
+}
+// end catch_errno_guard.cpp
+// start catch_exception_translator_registry.cpp
+
+// start catch_exception_translator_registry.h
+
+#include <vector>
+#include <string>
+#include <memory>
+
+namespace Catch {
+
+    class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry {
+    public:
+        ~ExceptionTranslatorRegistry();
+        virtual void registerTranslator( const IExceptionTranslator* translator );
+        virtual std::string translateActiveException() const override;
+        std::string tryTranslators() const;
+
+    private:
+        std::vector<std::unique_ptr<IExceptionTranslator const>> m_translators;
+    };
+}
+
+// end catch_exception_translator_registry.h
+#ifdef __OBJC__
+#import "Foundation/Foundation.h"
+#endif
+
+namespace Catch {
+
+    ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() {
+    }
+
+    void ExceptionTranslatorRegistry::registerTranslator( const IExceptionTranslator* translator ) {
+        m_translators.push_back( std::unique_ptr<const IExceptionTranslator>( translator ) );
+    }
+
+#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
+    std::string ExceptionTranslatorRegistry::translateActiveException() const {
+        try {
+#ifdef __OBJC__
+            // In Objective-C try objective-c exceptions first
+            @try {
+                return tryTranslators();
+            }
+            @catch (NSException *exception) {
+                return Catch::Detail::stringify( [exception description] );
+            }
+#else
+            // Compiling a mixed mode project with MSVC means that CLR
+            // exceptions will be caught in (...) as well. However, these
+            // do not fill-in std::current_exception and thus lead to crash
+            // when attempting rethrow.
+            // /EHa switch also causes structured exceptions to be caught
+            // here, but they fill-in current_exception properly, so
+            // at worst the output should be a little weird, instead of
+            // causing a crash.
+            if (std::current_exception() == nullptr) {
+                return "Non C++ exception. Possibly a CLR exception.";
+            }
+            return tryTranslators();
+#endif
+        }
+        catch( TestFailureException& ) {
+            std::rethrow_exception(std::current_exception());
+        }
+        catch( std::exception& ex ) {
+            return ex.what();
+        }
+        catch( std::string& msg ) {
+            return msg;
+        }
+        catch( const char* msg ) {
+            return msg;
+        }
+        catch(...) {
+            return "Unknown exception";
+        }
+    }
+
+#else // ^^ Exceptions are enabled // Exceptions are disabled vv
+    std::string ExceptionTranslatorRegistry::translateActiveException() const {
+        CATCH_INTERNAL_ERROR("Attempted to translate active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!");
+    }
+#endif
+
+    std::string ExceptionTranslatorRegistry::tryTranslators() const {
+        if( m_translators.empty() )
+            std::rethrow_exception(std::current_exception());
+        else
+            return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() );
+    }
+}
+// end catch_exception_translator_registry.cpp
+// start catch_fatal_condition.cpp
+
+#if defined(__GNUC__)
+#    pragma GCC diagnostic push
+#    pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+#endif
+
+#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS )
+
+namespace {
+    // Report the error condition
+    void reportFatal( char const * const message ) {
+        Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message );
+    }
+}
+
+#endif // signals/SEH handling
+
+#if defined( CATCH_CONFIG_WINDOWS_SEH )
+
+namespace Catch {
+    struct SignalDefs { DWORD id; const char* name; };
+
+    // There is no 1-1 mapping between signals and windows exceptions.
+    // Windows can easily distinguish between SO and SigSegV,
+    // but SigInt, SigTerm, etc are handled differently.
+    static SignalDefs signalDefs[] = {
+        { EXCEPTION_ILLEGAL_INSTRUCTION,  "SIGILL - Illegal instruction signal" },
+        { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" },
+        { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" },
+        { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" },
+    };
+
+    LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) {
+        for (auto const& def : signalDefs) {
+            if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) {
+                reportFatal(def.name);
+            }
+        }
+        // If its not an exception we care about, pass it along.
+        // This stops us from eating debugger breaks etc.
+        return EXCEPTION_CONTINUE_SEARCH;
+    }
+
+    FatalConditionHandler::FatalConditionHandler() {
+        isSet = true;
+        // 32k seems enough for Catch to handle stack overflow,
+        // but the value was found experimentally, so there is no strong guarantee
+        guaranteeSize = 32 * 1024;
+        exceptionHandlerHandle = nullptr;
+        // Register as first handler in current chain
+        exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException);
+        // Pass in guarantee size to be filled
+        SetThreadStackGuarantee(&guaranteeSize);
+    }
+
+    void FatalConditionHandler::reset() {
+        if (isSet) {
+            RemoveVectoredExceptionHandler(exceptionHandlerHandle);
+            SetThreadStackGuarantee(&guaranteeSize);
+            exceptionHandlerHandle = nullptr;
+            isSet = false;
+        }
+    }
+
+    FatalConditionHandler::~FatalConditionHandler() {
+        reset();
+    }
+
+bool FatalConditionHandler::isSet = false;
+ULONG FatalConditionHandler::guaranteeSize = 0;
+PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr;
+
+} // namespace Catch
+
+#elif defined( CATCH_CONFIG_POSIX_SIGNALS )
+
+namespace Catch {
+
+    struct SignalDefs {
+        int id;
+        const char* name;
+    };
+
+    // 32kb for the alternate stack seems to be sufficient. However, this value
+    // is experimentally determined, so that's not guaranteed.
+    constexpr static std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ;
+
+    static SignalDefs signalDefs[] = {
+        { SIGINT,  "SIGINT - Terminal interrupt signal" },
+        { SIGILL,  "SIGILL - Illegal instruction signal" },
+        { SIGFPE,  "SIGFPE - Floating point error signal" },
+        { SIGSEGV, "SIGSEGV - Segmentation violation signal" },
+        { SIGTERM, "SIGTERM - Termination request signal" },
+        { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" }
+    };
+
+    void FatalConditionHandler::handleSignal( int sig ) {
+        char const * name = "<unknown signal>";
+        for (auto const& def : signalDefs) {
+            if (sig == def.id) {
+                name = def.name;
+                break;
+            }
+        }
+        reset();
+        reportFatal(name);
+        raise( sig );
+    }
+
+    FatalConditionHandler::FatalConditionHandler() {
+        isSet = true;
+        stack_t sigStack;
+        sigStack.ss_sp = altStackMem;
+        sigStack.ss_size = sigStackSize;
+        sigStack.ss_flags = 0;
+        sigaltstack(&sigStack, &oldSigStack);
+        struct sigaction sa = { };
+
+        sa.sa_handler = handleSignal;
+        sa.sa_flags = SA_ONSTACK;
+        for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) {
+            sigaction(signalDefs[i].id, &sa, &oldSigActions[i]);
+        }
+    }
+
+    FatalConditionHandler::~FatalConditionHandler() {
+        reset();
+    }
+
+    void FatalConditionHandler::reset() {
+        if( isSet ) {
+            // Set signals back to previous values -- hopefully nobody overwrote them in the meantime
+            for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) {
+                sigaction(signalDefs[i].id, &oldSigActions[i], nullptr);
+            }
+            // Return the old stack
+            sigaltstack(&oldSigStack, nullptr);
+            isSet = false;
+        }
+    }
+
+    bool FatalConditionHandler::isSet = false;
+    struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {};
+    stack_t FatalConditionHandler::oldSigStack = {};
+    char FatalConditionHandler::altStackMem[sigStackSize] = {};
+
+} // namespace Catch
+
+#else
+
+namespace Catch {
+    void FatalConditionHandler::reset() {}
+}
+
+#endif // signals/SEH handling
+
+#if defined(__GNUC__)
+#    pragma GCC diagnostic pop
+#endif
+// end catch_fatal_condition.cpp
+// start catch_generators.cpp
+
+// start catch_random_number_generator.h
+
+#include <algorithm>
+#include <random>
+
+namespace Catch {
+
+    struct IConfig;
+
+    std::mt19937& rng();
+    void seedRng( IConfig const& config );
+    unsigned int rngSeed();
+
+}
+
+// end catch_random_number_generator.h
+#include <limits>
+#include <set>
+
+namespace Catch {
+
+IGeneratorTracker::~IGeneratorTracker() {}
+
+namespace Generators {
+
+    GeneratorBase::~GeneratorBase() {}
+
+    std::vector<size_t> randomiseIndices( size_t selectionSize, size_t sourceSize ) {
+
+        assert( selectionSize <= sourceSize );
+        std::vector<size_t> indices;
+        indices.reserve( selectionSize );
+        std::uniform_int_distribution<size_t> uid( 0, sourceSize-1 );
+
+        std::set<size_t> seen;
+        // !TBD: improve this algorithm
+        while( indices.size() < selectionSize ) {
+            auto index = uid( rng() );
+            if( seen.insert( index ).second )
+                indices.push_back( index );
+        }
+        return indices;
+    }
+
+    auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& {
+        return getResultCapture().acquireGeneratorTracker( lineInfo );
+    }
+
+    template<>
+    auto all<int>() -> Generator<int> {
+        return range( std::numeric_limits<int>::min(), std::numeric_limits<int>::max() );
+    }
+
+} // namespace Generators
+} // namespace Catch
+// end catch_generators.cpp
+// start catch_interfaces_capture.cpp
+
+namespace Catch {
+    IResultCapture::~IResultCapture() = default;
+}
+// end catch_interfaces_capture.cpp
+// start catch_interfaces_config.cpp
+
+namespace Catch {
+    IConfig::~IConfig() = default;
+}
+// end catch_interfaces_config.cpp
+// start catch_interfaces_exception.cpp
+
+namespace Catch {
+    IExceptionTranslator::~IExceptionTranslator() = default;
+    IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default;
+}
+// end catch_interfaces_exception.cpp
+// start catch_interfaces_registry_hub.cpp
+
+namespace Catch {
+    IRegistryHub::~IRegistryHub() = default;
+    IMutableRegistryHub::~IMutableRegistryHub() = default;
+}
+// end catch_interfaces_registry_hub.cpp
+// start catch_interfaces_reporter.cpp
+
+// start catch_reporter_listening.h
+
+namespace Catch {
+
+    class ListeningReporter : public IStreamingReporter {
+        using Reporters = std::vector<IStreamingReporterPtr>;
+        Reporters m_listeners;
+        IStreamingReporterPtr m_reporter = nullptr;
+        ReporterPreferences m_preferences;
+
+    public:
+        ListeningReporter();
+
+        void addListener( IStreamingReporterPtr&& listener );
+        void addReporter( IStreamingReporterPtr&& reporter );
+
+    public: // IStreamingReporter
+
+        ReporterPreferences getPreferences() const override;
+
+        void noMatchingTestCases( std::string const& spec ) override;
+
+        static std::set<Verbosity> getSupportedVerbosities();
+
+        void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override;
+        void benchmarkEnded( BenchmarkStats const& benchmarkStats ) override;
+
+        void testRunStarting( TestRunInfo const& testRunInfo ) override;
+        void testGroupStarting( GroupInfo const& groupInfo ) override;
+        void testCaseStarting( TestCaseInfo const& testInfo ) override;
+        void sectionStarting( SectionInfo const& sectionInfo ) override;
+        void assertionStarting( AssertionInfo const& assertionInfo ) override;
+
+        // The return value indicates if the messages buffer should be cleared:
+        bool assertionEnded( AssertionStats const& assertionStats ) override;
+        void sectionEnded( SectionStats const& sectionStats ) override;
+        void testCaseEnded( TestCaseStats const& testCaseStats ) override;
+        void testGroupEnded( TestGroupStats const& testGroupStats ) override;
+        void testRunEnded( TestRunStats const& testRunStats ) override;
+
+        void skipTest( TestCaseInfo const& testInfo ) override;
+        bool isMulti() const override;
+
+    };
+
+} // end namespace Catch
+
+// end catch_reporter_listening.h
+namespace Catch {
+
+    ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig )
+    :   m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {}
+
+    ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream )
+    :   m_stream( &_stream ), m_fullConfig( _fullConfig ) {}
+
+    std::ostream& ReporterConfig::stream() const { return *m_stream; }
+    IConfigPtr ReporterConfig::fullConfig() const { return m_fullConfig; }
+
+    TestRunInfo::TestRunInfo( std::string const& _name ) : name( _name ) {}
+
+    GroupInfo::GroupInfo(  std::string const& _name,
+                           std::size_t _groupIndex,
+                           std::size_t _groupsCount )
+    :   name( _name ),
+        groupIndex( _groupIndex ),
+        groupsCounts( _groupsCount )
+    {}
+
+     AssertionStats::AssertionStats( AssertionResult const& _assertionResult,
+                                     std::vector<MessageInfo> const& _infoMessages,
+                                     Totals const& _totals )
+    :   assertionResult( _assertionResult ),
+        infoMessages( _infoMessages ),
+        totals( _totals )
+    {
+        assertionResult.m_resultData.lazyExpression.m_transientExpression = _assertionResult.m_resultData.lazyExpression.m_transientExpression;
+
+        if( assertionResult.hasMessage() ) {
+            // Copy message into messages list.
+            // !TBD This should have been done earlier, somewhere
+            MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() );
+            builder << assertionResult.getMessage();
+            builder.m_info.message = builder.m_stream.str();
+
+            infoMessages.push_back( builder.m_info );
+        }
+    }
+
+     AssertionStats::~AssertionStats() = default;
+
+    SectionStats::SectionStats(  SectionInfo const& _sectionInfo,
+                                 Counts const& _assertions,
+                                 double _durationInSeconds,
+                                 bool _missingAssertions )
+    :   sectionInfo( _sectionInfo ),
+        assertions( _assertions ),
+        durationInSeconds( _durationInSeconds ),
+        missingAssertions( _missingAssertions )
+    {}
+
+    SectionStats::~SectionStats() = default;
+
+    TestCaseStats::TestCaseStats(  TestCaseInfo const& _testInfo,
+                                   Totals const& _totals,
+                                   std::string const& _stdOut,
+                                   std::string const& _stdErr,
+                                   bool _aborting )
+    : testInfo( _testInfo ),
+        totals( _totals ),
+        stdOut( _stdOut ),
+        stdErr( _stdErr ),
+        aborting( _aborting )
+    {}
+
+    TestCaseStats::~TestCaseStats() = default;
+
+    TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo,
+                                    Totals const& _totals,
+                                    bool _aborting )
+    :   groupInfo( _groupInfo ),
+        totals( _totals ),
+        aborting( _aborting )
+    {}
+
+    TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo )
+    :   groupInfo( _groupInfo ),
+        aborting( false )
+    {}
+
+    TestGroupStats::~TestGroupStats() = default;
+
+    TestRunStats::TestRunStats(   TestRunInfo const& _runInfo,
+                    Totals const& _totals,
+                    bool _aborting )
+    :   runInfo( _runInfo ),
+        totals( _totals ),
+        aborting( _aborting )
+    {}
+
+    TestRunStats::~TestRunStats() = default;
+
+    void IStreamingReporter::fatalErrorEncountered( StringRef ) {}
+    bool IStreamingReporter::isMulti() const { return false; }
+
+    IReporterFactory::~IReporterFactory() = default;
+    IReporterRegistry::~IReporterRegistry() = default;
+
+} // end namespace Catch
+// end catch_interfaces_reporter.cpp
+// start catch_interfaces_runner.cpp
+
+namespace Catch {
+    IRunner::~IRunner() = default;
+}
+// end catch_interfaces_runner.cpp
+// start catch_interfaces_testcase.cpp
+
+namespace Catch {
+    ITestInvoker::~ITestInvoker() = default;
+    ITestCaseRegistry::~ITestCaseRegistry() = default;
+}
+// end catch_interfaces_testcase.cpp
+// start catch_leak_detector.cpp
+
+#ifdef CATCH_CONFIG_WINDOWS_CRTDBG
+#include <crtdbg.h>
+
+namespace Catch {
+
+    LeakDetector::LeakDetector() {
+        int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
+        flag |= _CRTDBG_LEAK_CHECK_DF;
+        flag |= _CRTDBG_ALLOC_MEM_DF;
+        _CrtSetDbgFlag(flag);
+        _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
+        _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
+        // Change this to leaking allocation's number to break there
+        _CrtSetBreakAlloc(-1);
+    }
+}
+
+#else
+
+    Catch::LeakDetector::LeakDetector() {}
+
+#endif
+
+Catch::LeakDetector::~LeakDetector() {
+    Catch::cleanUp();
+}
+// end catch_leak_detector.cpp
+// start catch_list.cpp
+
+// start catch_list.h
+
+#include <set>
+
+namespace Catch {
+
+    std::size_t listTests( Config const& config );
+
+    std::size_t listTestsNamesOnly( Config const& config );
+
+    struct TagInfo {
+        void add( std::string const& spelling );
+        std::string all() const;
+
+        std::set<std::string> spellings;
+        std::size_t count = 0;
+    };
+
+    std::size_t listTags( Config const& config );
+
+    std::size_t listReporters();
+
+    Option<std::size_t> list( Config const& config );
+
+} // end namespace Catch
+
+// end catch_list.h
+// start catch_text.h
+
+namespace Catch {
+    using namespace clara::TextFlow;
+}
+
+// end catch_text.h
+#include <limits>
+#include <algorithm>
+#include <iomanip>
+
+namespace Catch {
+
+    std::size_t listTests( Config const& config ) {
+        TestSpec testSpec = config.testSpec();
+        if( config.hasTestFilters() )
+            Catch::cout() << "Matching test cases:\n";
+        else {
+            Catch::cout() << "All available test cases:\n";
+        }
+
+        auto matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
+        for( auto const& testCaseInfo : matchedTestCases ) {
+            Colour::Code colour = testCaseInfo.isHidden()
+                ? Colour::SecondaryText
+                : Colour::None;
+            Colour colourGuard( colour );
+
+            Catch::cout() << Column( testCaseInfo.name ).initialIndent( 2 ).indent( 4 ) << "\n";
+            if( config.verbosity() >= Verbosity::High ) {
+                Catch::cout() << Column( Catch::Detail::stringify( testCaseInfo.lineInfo ) ).indent(4) << std::endl;
+                std::string description = testCaseInfo.description;
+                if( description.empty() )
+                    description = "(NO DESCRIPTION)";
+                Catch::cout() << Column( description ).indent(4) << std::endl;
+            }
+            if( !testCaseInfo.tags.empty() )
+                Catch::cout() << Column( testCaseInfo.tagsAsString() ).indent( 6 ) << "\n";
+        }
+
+        if( !config.hasTestFilters() )
+            Catch::cout() << pluralise( matchedTestCases.size(), "test case" ) << '\n' << std::endl;
+        else
+            Catch::cout() << pluralise( matchedTestCases.size(), "matching test case" ) << '\n' << std::endl;
+        return matchedTestCases.size();
+    }
+
+    std::size_t listTestsNamesOnly( Config const& config ) {
+        TestSpec testSpec = config.testSpec();
+        std::size_t matchedTests = 0;
+        std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
+        for( auto const& testCaseInfo : matchedTestCases ) {
+            matchedTests++;
+            if( startsWith( testCaseInfo.name, '#' ) )
+               Catch::cout() << '"' << testCaseInfo.name << '"';
+            else
+               Catch::cout() << testCaseInfo.name;
+            if ( config.verbosity() >= Verbosity::High )
+                Catch::cout() << "\t@" << testCaseInfo.lineInfo;
+            Catch::cout() << std::endl;
+        }
+        return matchedTests;
+    }
+
+    void TagInfo::add( std::string const& spelling ) {
+        ++count;
+        spellings.insert( spelling );
+    }
+
+    std::string TagInfo::all() const {
+        std::string out;
+        for( auto const& spelling : spellings )
+            out += "[" + spelling + "]";
+        return out;
+    }
+
+    std::size_t listTags( Config const& config ) {
+        TestSpec testSpec = config.testSpec();
+        if( config.hasTestFilters() )
+            Catch::cout() << "Tags for matching test cases:\n";
+        else {
+            Catch::cout() << "All available tags:\n";
+        }
+
+        std::map<std::string, TagInfo> tagCounts;
+
+        std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
+        for( auto const& testCase : matchedTestCases ) {
+            for( auto const& tagName : testCase.getTestCaseInfo().tags ) {
+                std::string lcaseTagName = toLower( tagName );
+                auto countIt = tagCounts.find( lcaseTagName );
+                if( countIt == tagCounts.end() )
+                    countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first;
+                countIt->second.add( tagName );
+            }
+        }
+
+        for( auto const& tagCount : tagCounts ) {
+            ReusableStringStream rss;
+            rss << "  " << std::setw(2) << tagCount.second.count << "  ";
+            auto str = rss.str();
+            auto wrapper = Column( tagCount.second.all() )
+                                                    .initialIndent( 0 )
+                                                    .indent( str.size() )
+                                                    .width( CATCH_CONFIG_CONSOLE_WIDTH-10 );
+            Catch::cout() << str << wrapper << '\n';
+        }
+        Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl;
+        return tagCounts.size();
+    }
+
+    std::size_t listReporters() {
+        Catch::cout() << "Available reporters:\n";
+        IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories();
+        std::size_t maxNameLen = 0;
+        for( auto const& factoryKvp : factories )
+            maxNameLen = (std::max)( maxNameLen, factoryKvp.first.size() );
+
+        for( auto const& factoryKvp : factories ) {
+            Catch::cout()
+                    << Column( factoryKvp.first + ":" )
+                            .indent(2)
+                            .width( 5+maxNameLen )
+                    +  Column( factoryKvp.second->getDescription() )
+                            .initialIndent(0)
+                            .indent(2)
+                            .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 )
+                    << "\n";
+        }
+        Catch::cout() << std::endl;
+        return factories.size();
+    }
+
+    Option<std::size_t> list( Config const& config ) {
+        Option<std::size_t> listedCount;
+        if( config.listTests() )
+            listedCount = listedCount.valueOr(0) + listTests( config );
+        if( config.listTestNamesOnly() )
+            listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config );
+        if( config.listTags() )
+            listedCount = listedCount.valueOr(0) + listTags( config );
+        if( config.listReporters() )
+            listedCount = listedCount.valueOr(0) + listReporters();
+        return listedCount;
+    }
+
+} // end namespace Catch
+// end catch_list.cpp
+// start catch_matchers.cpp
+
+namespace Catch {
+namespace Matchers {
+    namespace Impl {
+
+        std::string MatcherUntypedBase::toString() const {
+            if( m_cachedToString.empty() )
+                m_cachedToString = describe();
+            return m_cachedToString;
+        }
+
+        MatcherUntypedBase::~MatcherUntypedBase() = default;
+
+    } // namespace Impl
+} // namespace Matchers
+
+using namespace Matchers;
+using Matchers::Impl::MatcherBase;
+
+} // namespace Catch
+// end catch_matchers.cpp
+// start catch_matchers_floating.cpp
+
+// start catch_polyfills.hpp
+
+namespace Catch {
+    bool isnan(float f);
+    bool isnan(double d);
+}
+
+// end catch_polyfills.hpp
+// start catch_to_string.hpp
+
+#include <string>
+
+namespace Catch {
+    template <typename T>
+    std::string to_string(T const& t) {
+#if defined(CATCH_CONFIG_CPP11_TO_STRING)
+        return std::to_string(t);
+#else
+        ReusableStringStream rss;
+        rss << t;
+        return rss.str();
+#endif
+    }
+} // end namespace Catch
+
+// end catch_to_string.hpp
+#include <cstdlib>
+#include <cstdint>
+#include <cstring>
+
+namespace Catch {
+namespace Matchers {
+namespace Floating {
+enum class FloatingPointKind : uint8_t {
+    Float,
+    Double
+};
+}
+}
+}
+
+namespace {
+
+template <typename T>
+struct Converter;
+
+template <>
+struct Converter<float> {
+    static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated");
+    Converter(float f) {
+        std::memcpy(&i, &f, sizeof(f));
+    }
+    int32_t i;
+};
+
+template <>
+struct Converter<double> {
+    static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated");
+    Converter(double d) {
+        std::memcpy(&i, &d, sizeof(d));
+    }
+    int64_t i;
+};
+
+template <typename T>
+auto convert(T t) -> Converter<T> {
+    return Converter<T>(t);
+}
+
+template <typename FP>
+bool almostEqualUlps(FP lhs, FP rhs, int maxUlpDiff) {
+    // Comparison with NaN should always be false.
+    // This way we can rule it out before getting into the ugly details
+    if (Catch::isnan(lhs) || Catch::isnan(rhs)) {
+        return false;
+    }
+
+    auto lc = convert(lhs);
+    auto rc = convert(rhs);
+
+    if ((lc.i < 0) != (rc.i < 0)) {
+        // Potentially we can have +0 and -0
+        return lhs == rhs;
+    }
+
+    auto ulpDiff = std::abs(lc.i - rc.i);
+    return ulpDiff <= maxUlpDiff;
+}
+
+}
+
+namespace Catch {
+namespace Matchers {
+namespace Floating {
+    WithinAbsMatcher::WithinAbsMatcher(double target, double margin)
+        :m_target{ target }, m_margin{ margin } {
+        CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.'
+            << " Margin has to be non-negative.");
+    }
+
+    // Performs equivalent check of std::fabs(lhs - rhs) <= margin
+    // But without the subtraction to allow for INFINITY in comparison
+    bool WithinAbsMatcher::match(double const& matchee) const {
+        return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee);
+    }
+
+    std::string WithinAbsMatcher::describe() const {
+        return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target);
+    }
+
+    WithinUlpsMatcher::WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType)
+        :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } {
+        CATCH_ENFORCE(ulps >= 0, "Invalid ULP setting: " << ulps << '.'
+            << " ULPs have to be non-negative.");
+    }
+
+#if defined(__clang__)
+#pragma clang diagnostic push
+// Clang <3.5 reports on the default branch in the switch below
+#pragma clang diagnostic ignored "-Wunreachable-code"
+#endif
+
+    bool WithinUlpsMatcher::match(double const& matchee) const {
+        switch (m_type) {
+        case FloatingPointKind::Float:
+            return almostEqualUlps<float>(static_cast<float>(matchee), static_cast<float>(m_target), m_ulps);
+        case FloatingPointKind::Double:
+            return almostEqualUlps<double>(matchee, m_target, m_ulps);
+        default:
+            CATCH_INTERNAL_ERROR( "Unknown FloatingPointKind value" );
+        }
+    }
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
+    std::string WithinUlpsMatcher::describe() const {
+        return "is within " + Catch::to_string(m_ulps) + " ULPs of " + ::Catch::Detail::stringify(m_target) + ((m_type == FloatingPointKind::Float)? "f" : "");
+    }
+
+}// namespace Floating
+
+Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff) {
+    return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double);
+}
+
+Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff) {
+    return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float);
+}
+
+Floating::WithinAbsMatcher WithinAbs(double target, double margin) {
+    return Floating::WithinAbsMatcher(target, margin);
+}
+
+} // namespace Matchers
+} // namespace Catch
+
+// end catch_matchers_floating.cpp
+// start catch_matchers_generic.cpp
+
+std::string Catch::Matchers::Generic::Detail::finalizeDescription(const std::string& desc) {
+    if (desc.empty()) {
+        return "matches undescribed predicate";
+    } else {
+        return "matches predicate: \"" + desc + '"';
+    }
+}
+// end catch_matchers_generic.cpp
+// start catch_matchers_string.cpp
+
+#include <regex>
+
+namespace Catch {
+namespace Matchers {
+
+    namespace StdString {
+
+        CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity )
+        :   m_caseSensitivity( caseSensitivity ),
+            m_str( adjustString( str ) )
+        {}
+        std::string CasedString::adjustString( std::string const& str ) const {
+            return m_caseSensitivity == CaseSensitive::No
+                   ? toLower( str )
+                   : str;
+        }
+        std::string CasedString::caseSensitivitySuffix() const {
+            return m_caseSensitivity == CaseSensitive::No
+                   ? " (case insensitive)"
+                   : std::string();
+        }
+
+        StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator )
+        : m_comparator( comparator ),
+          m_operation( operation ) {
+        }
+
+        std::string StringMatcherBase::describe() const {
+            std::string description;
+            description.reserve(5 + m_operation.size() + m_comparator.m_str.size() +
+                                        m_comparator.caseSensitivitySuffix().size());
+            description += m_operation;
+            description += ": \"";
+            description += m_comparator.m_str;
+            description += "\"";
+            description += m_comparator.caseSensitivitySuffix();
+            return description;
+        }
+
+        EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {}
+
+        bool EqualsMatcher::match( std::string const& source ) const {
+            return m_comparator.adjustString( source ) == m_comparator.m_str;
+        }
+
+        ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {}
+
+        bool ContainsMatcher::match( std::string const& source ) const {
+            return contains( m_comparator.adjustString( source ), m_comparator.m_str );
+        }
+
+        StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {}
+
+        bool StartsWithMatcher::match( std::string const& source ) const {
+            return startsWith( m_comparator.adjustString( source ), m_comparator.m_str );
+        }
+
+        EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {}
+
+        bool EndsWithMatcher::match( std::string const& source ) const {
+            return endsWith( m_comparator.adjustString( source ), m_comparator.m_str );
+        }
+
+        RegexMatcher::RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity): m_regex(std::move(regex)), m_caseSensitivity(caseSensitivity) {}
+
+        bool RegexMatcher::match(std::string const& matchee) const {
+            auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway
+            if (m_caseSensitivity == CaseSensitive::Choice::No) {
+                flags |= std::regex::icase;
+            }
+            auto reg = std::regex(m_regex, flags);
+            return std::regex_match(matchee, reg);
+        }
+
+        std::string RegexMatcher::describe() const {
+            return "matches " + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Choice::Yes)? " case sensitively" : " case insensitively");
+        }
+
+    } // namespace StdString
+
+    StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) {
+        return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) );
+    }
+    StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) {
+        return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) );
+    }
+    StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) {
+        return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) );
+    }
+    StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) {
+        return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) );
+    }
+
+    StdString::RegexMatcher Matches(std::string const& regex, CaseSensitive::Choice caseSensitivity) {
+        return StdString::RegexMatcher(regex, caseSensitivity);
+    }
+
+} // namespace Matchers
+} // namespace Catch
+// end catch_matchers_string.cpp
+// start catch_message.cpp
+
+// start catch_uncaught_exceptions.h
+
+namespace Catch {
+    bool uncaught_exceptions();
+} // end namespace Catch
+
+// end catch_uncaught_exceptions.h
+#include <cassert>
+#include <stack>
+
+namespace Catch {
+
+    MessageInfo::MessageInfo(   StringRef const& _macroName,
+                                SourceLineInfo const& _lineInfo,
+                                ResultWas::OfType _type )
+    :   macroName( _macroName ),
+        lineInfo( _lineInfo ),
+        type( _type ),
+        sequence( ++globalCount )
+    {}
+
+    bool MessageInfo::operator==( MessageInfo const& other ) const {
+        return sequence == other.sequence;
+    }
+
+    bool MessageInfo::operator<( MessageInfo const& other ) const {
+        return sequence < other.sequence;
+    }
+
+    // This may need protecting if threading support is added
+    unsigned int MessageInfo::globalCount = 0;
+
+    ////////////////////////////////////////////////////////////////////////////
+
+    Catch::MessageBuilder::MessageBuilder( StringRef const& macroName,
+                                           SourceLineInfo const& lineInfo,
+                                           ResultWas::OfType type )
+        :m_info(macroName, lineInfo, type) {}
+
+    ////////////////////////////////////////////////////////////////////////////
+
+    ScopedMessage::ScopedMessage( MessageBuilder const& builder )
+    : m_info( builder.m_info )
+    {
+        m_info.message = builder.m_stream.str();
+        getResultCapture().pushScopedMessage( m_info );
+    }
+
+    ScopedMessage::~ScopedMessage() {
+        if ( !uncaught_exceptions() ){
+            getResultCapture().popScopedMessage(m_info);
+        }
+    }
+
+    Capturer::Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ) {
+        auto trimmed = [&] (size_t start, size_t end) {
+            while (names[start] == ',' || isspace(names[start])) {
+                ++start;
+            }
+            while (names[end] == ',' || isspace(names[end])) {
+                --end;
+            }
+            return names.substr(start, end - start + 1);
+        };
+
+        size_t start = 0;
+        std::stack<char> openings;
+        for (size_t pos = 0; pos < names.size(); ++pos) {
+            char c = names[pos];
+            switch (c) {
+            case '[':
+            case '{':
+            case '(':
+            // It is basically impossible to disambiguate between
+            // comparison and start of template args in this context
+//            case '<':
+                openings.push(c);
+                break;
+            case ']':
+            case '}':
+            case ')':
+//           case '>':
+                openings.pop();
+                break;
+            case ',':
+                if (start != pos && openings.size() == 0) {
+                    m_messages.emplace_back(macroName, lineInfo, resultType);
+                    m_messages.back().message = trimmed(start, pos);
+                    m_messages.back().message += " := ";
+                    start = pos;
+                }
+            }
+        }
+        assert(openings.size() == 0 && "Mismatched openings");
+        m_messages.emplace_back(macroName, lineInfo, resultType);
+        m_messages.back().message = trimmed(start, names.size() - 1);
+        m_messages.back().message += " := ";
+    }
+    Capturer::~Capturer() {
+        if ( !uncaught_exceptions() ){
+            assert( m_captured == m_messages.size() );
+            for( size_t i = 0; i < m_captured; ++i  )
+                m_resultCapture.popScopedMessage( m_messages[i] );
+        }
+    }
+
+    void Capturer::captureValue( size_t index, std::string const& value ) {
+        assert( index < m_messages.size() );
+        m_messages[index].message += value;
+        m_resultCapture.pushScopedMessage( m_messages[index] );
+        m_captured++;
+    }
+
+} // end namespace Catch
+// end catch_message.cpp
+// start catch_output_redirect.cpp
+
+// start catch_output_redirect.h
+#ifndef TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H
+#define TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H
+
+#include <cstdio>
+#include <iosfwd>
+#include <string>
+
+namespace Catch {
+
+    class RedirectedStream {
+        std::ostream& m_originalStream;
+        std::ostream& m_redirectionStream;
+        std::streambuf* m_prevBuf;
+
+    public:
+        RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream );
+        ~RedirectedStream();
+    };
+
+    class RedirectedStdOut {
+        ReusableStringStream m_rss;
+        RedirectedStream m_cout;
+    public:
+        RedirectedStdOut();
+        auto str() const -> std::string;
+    };
+
+    // StdErr has two constituent streams in C++, std::cerr and std::clog
+    // This means that we need to redirect 2 streams into 1 to keep proper
+    // order of writes
+    class RedirectedStdErr {
+        ReusableStringStream m_rss;
+        RedirectedStream m_cerr;
+        RedirectedStream m_clog;
+    public:
+        RedirectedStdErr();
+        auto str() const -> std::string;
+    };
+
+#if defined(CATCH_CONFIG_NEW_CAPTURE)
+
+    // Windows's implementation of std::tmpfile is terrible (it tries
+    // to create a file inside system folder, thus requiring elevated
+    // privileges for the binary), so we have to use tmpnam(_s) and
+    // create the file ourselves there.
+    class TempFile {
+    public:
+        TempFile(TempFile const&) = delete;
+        TempFile& operator=(TempFile const&) = delete;
+        TempFile(TempFile&&) = delete;
+        TempFile& operator=(TempFile&&) = delete;
+
+        TempFile();
+        ~TempFile();
+
+        std::FILE* getFile();
+        std::string getContents();
+
+    private:
+        std::FILE* m_file = nullptr;
+    #if defined(_MSC_VER)
+        char m_buffer[L_tmpnam] = { 0 };
+    #endif
+    };
+
+    class OutputRedirect {
+    public:
+        OutputRedirect(OutputRedirect const&) = delete;
+        OutputRedirect& operator=(OutputRedirect const&) = delete;
+        OutputRedirect(OutputRedirect&&) = delete;
+        OutputRedirect& operator=(OutputRedirect&&) = delete;
+
+        OutputRedirect(std::string& stdout_dest, std::string& stderr_dest);
+        ~OutputRedirect();
+
+    private:
+        int m_originalStdout = -1;
+        int m_originalStderr = -1;
+        TempFile m_stdoutFile;
+        TempFile m_stderrFile;
+        std::string& m_stdoutDest;
+        std::string& m_stderrDest;
+    };
+
+#endif
+
+} // end namespace Catch
+
+#endif // TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H
+// end catch_output_redirect.h
+#include <cstdio>
+#include <cstring>
+#include <fstream>
+#include <sstream>
+#include <stdexcept>
+
+#if defined(CATCH_CONFIG_NEW_CAPTURE)
+    #if defined(_MSC_VER)
+    #include <io.h>      //_dup and _dup2
+    #define dup _dup
+    #define dup2 _dup2
+    #define fileno _fileno
+    #else
+    #include <unistd.h>  // dup and dup2
+    #endif
+#endif
+
+namespace Catch {
+
+    RedirectedStream::RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream )
+    :   m_originalStream( originalStream ),
+        m_redirectionStream( redirectionStream ),
+        m_prevBuf( m_originalStream.rdbuf() )
+    {
+        m_originalStream.rdbuf( m_redirectionStream.rdbuf() );
+    }
+
+    RedirectedStream::~RedirectedStream() {
+        m_originalStream.rdbuf( m_prevBuf );
+    }
+
+    RedirectedStdOut::RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {}
+    auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); }
+
+    RedirectedStdErr::RedirectedStdErr()
+    :   m_cerr( Catch::cerr(), m_rss.get() ),
+        m_clog( Catch::clog(), m_rss.get() )
+    {}
+    auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); }
+
+#if defined(CATCH_CONFIG_NEW_CAPTURE)
+
+#if defined(_MSC_VER)
+    TempFile::TempFile() {
+        if (tmpnam_s(m_buffer)) {
+            CATCH_RUNTIME_ERROR("Could not get a temp filename");
+        }
+        if (fopen_s(&m_file, m_buffer, "w")) {
+            char buffer[100];
+            if (strerror_s(buffer, errno)) {
+                CATCH_RUNTIME_ERROR("Could not translate errno to a string");
+            }
+            CATCH_RUNTIME_ERROR("Coul dnot open the temp file: '" << m_buffer << "' because: " << buffer);
+        }
+    }
+#else
+    TempFile::TempFile() {
+        m_file = std::tmpfile();
+        if (!m_file) {
+            CATCH_RUNTIME_ERROR("Could not create a temp file.");
+        }
+    }
+
+#endif
+
+    TempFile::~TempFile() {
+         // TBD: What to do about errors here?
+         std::fclose(m_file);
+         // We manually create the file on Windows only, on Linux
+         // it will be autodeleted
+#if defined(_MSC_VER)
+         std::remove(m_buffer);
+#endif
+    }
+
+    FILE* TempFile::getFile() {
+        return m_file;
+    }
+
+    std::string TempFile::getContents() {
+        std::stringstream sstr;
+        char buffer[100] = {};
+        std::rewind(m_file);
+        while (std::fgets(buffer, sizeof(buffer), m_file)) {
+            sstr << buffer;
+        }
+        return sstr.str();
+    }
+
+    OutputRedirect::OutputRedirect(std::string& stdout_dest, std::string& stderr_dest) :
+        m_originalStdout(dup(1)),
+        m_originalStderr(dup(2)),
+        m_stdoutDest(stdout_dest),
+        m_stderrDest(stderr_dest) {
+        dup2(fileno(m_stdoutFile.getFile()), 1);
+        dup2(fileno(m_stderrFile.getFile()), 2);
+    }
+
+    OutputRedirect::~OutputRedirect() {
+        Catch::cout() << std::flush;
+        fflush(stdout);
+        // Since we support overriding these streams, we flush cerr
+        // even though std::cerr is unbuffered
+        Catch::cerr() << std::flush;
+        Catch::clog() << std::flush;
+        fflush(stderr);
+
+        dup2(m_originalStdout, 1);
+        dup2(m_originalStderr, 2);
+
+        m_stdoutDest += m_stdoutFile.getContents();
+        m_stderrDest += m_stderrFile.getContents();
+    }
+
+#endif // CATCH_CONFIG_NEW_CAPTURE
+
+} // namespace Catch
+
+#if defined(CATCH_CONFIG_NEW_CAPTURE)
+    #if defined(_MSC_VER)
+    #undef dup
+    #undef dup2
+    #undef fileno
+    #endif
+#endif
+// end catch_output_redirect.cpp
+// start catch_polyfills.cpp
+
+#include <cmath>
+
+namespace Catch {
+
+#if !defined(CATCH_CONFIG_POLYFILL_ISNAN)
+    bool isnan(float f) {
+        return std::isnan(f);
+    }
+    bool isnan(double d) {
+        return std::isnan(d);
+    }
+#else
+    // For now we only use this for embarcadero
+    bool isnan(float f) {
+        return std::_isnan(f);
+    }
+    bool isnan(double d) {
+        return std::_isnan(d);
+    }
+#endif
+
+} // end namespace Catch
+// end catch_polyfills.cpp
+// start catch_random_number_generator.cpp
+
+namespace Catch {
+
+    std::mt19937& rng() {
+        static std::mt19937 s_rng;
+        return s_rng;
+    }
+
+    void seedRng( IConfig const& config ) {
+        if( config.rngSeed() != 0 ) {
+            std::srand( config.rngSeed() );
+            rng().seed( config.rngSeed() );
+        }
+    }
+
+    unsigned int rngSeed() {
+        return getCurrentContext().getConfig()->rngSeed();
+    }
+}
+// end catch_random_number_generator.cpp
+// start catch_registry_hub.cpp
+
+// start catch_test_case_registry_impl.h
+
+#include <vector>
+#include <set>
+#include <algorithm>
+#include <ios>
+
+namespace Catch {
+
+    class TestCase;
+    struct IConfig;
+
+    std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases );
+    bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config );
+
+    void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions );
+
+    std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config );
+    std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config );
+
+    class TestRegistry : public ITestCaseRegistry {
+    public:
+        virtual ~TestRegistry() = default;
+
+        virtual void registerTest( TestCase const& testCase );
+
+        std::vector<TestCase> const& getAllTests() const override;
+        std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const override;
+
+    private:
+        std::vector<TestCase> m_functions;
+        mutable RunTests::InWhatOrder m_currentSortOrder = RunTests::InDeclarationOrder;
+        mutable std::vector<TestCase> m_sortedFunctions;
+        std::size_t m_unnamedCount = 0;
+        std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised
+    };
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    class TestInvokerAsFunction : public ITestInvoker {
+        void(*m_testAsFunction)();
+    public:
+        TestInvokerAsFunction( void(*testAsFunction)() ) noexcept;
+
+        void invoke() const override;
+    };
+
+    std::string extractClassName( StringRef const& classOrQualifiedMethodName );
+
+    ///////////////////////////////////////////////////////////////////////////
+
+} // end namespace Catch
+
+// end catch_test_case_registry_impl.h
+// start catch_reporter_registry.h
+
+#include <map>
+
+namespace Catch {
+
+    class ReporterRegistry : public IReporterRegistry {
+
+    public:
+
+        ~ReporterRegistry() override;
+
+        IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const override;
+
+        void registerReporter( std::string const& name, IReporterFactoryPtr const& factory );
+        void registerListener( IReporterFactoryPtr const& factory );
+
+        FactoryMap const& getFactories() const override;
+        Listeners const& getListeners() const override;
+
+    private:
+        FactoryMap m_factories;
+        Listeners m_listeners;
+    };
+}
+
+// end catch_reporter_registry.h
+// start catch_tag_alias_registry.h
+
+// start catch_tag_alias.h
+
+#include <string>
+
+namespace Catch {
+
+    struct TagAlias {
+        TagAlias(std::string const& _tag, SourceLineInfo _lineInfo);
+
+        std::string tag;
+        SourceLineInfo lineInfo;
+    };
+
+} // end namespace Catch
+
+// end catch_tag_alias.h
+#include <map>
+
+namespace Catch {
+
+    class TagAliasRegistry : public ITagAliasRegistry {
+    public:
+        ~TagAliasRegistry() override;
+        TagAlias const* find( std::string const& alias ) const override;
+        std::string expandAliases( std::string const& unexpandedTestSpec ) const override;
+        void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo );
+
+    private:
+        std::map<std::string, TagAlias> m_registry;
+    };
+
+} // end namespace Catch
+
+// end catch_tag_alias_registry.h
+// start catch_startup_exception_registry.h
+
+#include <vector>
+#include <exception>
+
+namespace Catch {
+
+    class StartupExceptionRegistry {
+    public:
+        void add(std::exception_ptr const& exception) noexcept;
+        std::vector<std::exception_ptr> const& getExceptions() const noexcept;
+    private:
+        std::vector<std::exception_ptr> m_exceptions;
+    };
+
+} // end namespace Catch
+
+// end catch_startup_exception_registry.h
+// start catch_singletons.hpp
+
+namespace Catch {
+
+    struct ISingleton {
+        virtual ~ISingleton();
+    };
+
+    void addSingleton( ISingleton* singleton );
+    void cleanupSingletons();
+
+    template<typename SingletonImplT, typename InterfaceT = SingletonImplT, typename MutableInterfaceT = InterfaceT>
+    class Singleton : SingletonImplT, public ISingleton {
+
+        static auto getInternal() -> Singleton* {
+            static Singleton* s_instance = nullptr;
+            if( !s_instance ) {
+                s_instance = new Singleton;
+                addSingleton( s_instance );
+            }
+            return s_instance;
+        }
+
+    public:
+        static auto get() -> InterfaceT const& {
+            return *getInternal();
+        }
+        static auto getMutable() -> MutableInterfaceT& {
+            return *getInternal();
+        }
+    };
+
+} // namespace Catch
+
+// end catch_singletons.hpp
+namespace Catch {
+
+    namespace {
+
+        class RegistryHub : public IRegistryHub, public IMutableRegistryHub,
+                            private NonCopyable {
+
+        public: // IRegistryHub
+            RegistryHub() = default;
+            IReporterRegistry const& getReporterRegistry() const override {
+                return m_reporterRegistry;
+            }
+            ITestCaseRegistry const& getTestCaseRegistry() const override {
+                return m_testCaseRegistry;
+            }
+            IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const override {
+                return m_exceptionTranslatorRegistry;
+            }
+            ITagAliasRegistry const& getTagAliasRegistry() const override {
+                return m_tagAliasRegistry;
+            }
+            StartupExceptionRegistry const& getStartupExceptionRegistry() const override {
+                return m_exceptionRegistry;
+            }
+
+        public: // IMutableRegistryHub
+            void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) override {
+                m_reporterRegistry.registerReporter( name, factory );
+            }
+            void registerListener( IReporterFactoryPtr const& factory ) override {
+                m_reporterRegistry.registerListener( factory );
+            }
+            void registerTest( TestCase const& testInfo ) override {
+                m_testCaseRegistry.registerTest( testInfo );
+            }
+            void registerTranslator( const IExceptionTranslator* translator ) override {
+                m_exceptionTranslatorRegistry.registerTranslator( translator );
+            }
+            void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override {
+                m_tagAliasRegistry.add( alias, tag, lineInfo );
+            }
+            void registerStartupException() noexcept override {
+                m_exceptionRegistry.add(std::current_exception());
+            }
+
+        private:
+            TestRegistry m_testCaseRegistry;
+            ReporterRegistry m_reporterRegistry;
+            ExceptionTranslatorRegistry m_exceptionTranslatorRegistry;
+            TagAliasRegistry m_tagAliasRegistry;
+            StartupExceptionRegistry m_exceptionRegistry;
+        };
+    }
+
+    using RegistryHubSingleton = Singleton<RegistryHub, IRegistryHub, IMutableRegistryHub>;
+
+    IRegistryHub const& getRegistryHub() {
+        return RegistryHubSingleton::get();
+    }
+    IMutableRegistryHub& getMutableRegistryHub() {
+        return RegistryHubSingleton::getMutable();
+    }
+    void cleanUp() {
+        cleanupSingletons();
+        cleanUpContext();
+    }
+    std::string translateActiveException() {
+        return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException();
+    }
+
+} // end namespace Catch
+// end catch_registry_hub.cpp
+// start catch_reporter_registry.cpp
+
+namespace Catch {
+
+    ReporterRegistry::~ReporterRegistry() = default;
+
+    IStreamingReporterPtr ReporterRegistry::create( std::string const& name, IConfigPtr const& config ) const {
+        auto it =  m_factories.find( name );
+        if( it == m_factories.end() )
+            return nullptr;
+        return it->second->create( ReporterConfig( config ) );
+    }
+
+    void ReporterRegistry::registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) {
+        m_factories.emplace(name, factory);
+    }
+    void ReporterRegistry::registerListener( IReporterFactoryPtr const& factory ) {
+        m_listeners.push_back( factory );
+    }
+
+    IReporterRegistry::FactoryMap const& ReporterRegistry::getFactories() const {
+        return m_factories;
+    }
+    IReporterRegistry::Listeners const& ReporterRegistry::getListeners() const {
+        return m_listeners;
+    }
+
+}
+// end catch_reporter_registry.cpp
+// start catch_result_type.cpp
+
+namespace Catch {
+
+    bool isOk( ResultWas::OfType resultType ) {
+        return ( resultType & ResultWas::FailureBit ) == 0;
+    }
+    bool isJustInfo( int flags ) {
+        return flags == ResultWas::Info;
+    }
+
+    ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) {
+        return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) | static_cast<int>( rhs ) );
+    }
+
+    bool shouldContinueOnFailure( int flags )    { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; }
+    bool shouldSuppressFailure( int flags )      { return ( flags & ResultDisposition::SuppressFail ) != 0; }
+
+} // end namespace Catch
+// end catch_result_type.cpp
+// start catch_run_context.cpp
+
+#include <cassert>
+#include <algorithm>
+#include <sstream>
+
+namespace Catch {
+
+    namespace Generators {
+        struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker {
+            size_t m_index = static_cast<size_t>( -1 );
+            GeneratorBasePtr m_generator;
+
+            GeneratorTracker( TestCaseTracking::NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )
+            :   TrackerBase( nameAndLocation, ctx, parent )
+            {}
+            ~GeneratorTracker();
+
+            static GeneratorTracker& acquire( TrackerContext& ctx, TestCaseTracking::NameAndLocation const& nameAndLocation ) {
+                std::shared_ptr<GeneratorTracker> tracker;
+
+                ITracker& currentTracker = ctx.currentTracker();
+                if( TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) {
+                    assert( childTracker );
+                    assert( childTracker->isIndexTracker() );
+                    tracker = std::static_pointer_cast<GeneratorTracker>( childTracker );
+                }
+                else {
+                    tracker = std::make_shared<GeneratorTracker>( nameAndLocation, ctx, &currentTracker );
+                    currentTracker.addChild( tracker );
+                }
+
+                if( !ctx.completedCycle() && !tracker->isComplete() ) {
+                    if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun )
+                        tracker->moveNext();
+                    tracker->open();
+                }
+
+                return *tracker;
+            }
+
+            void moveNext() {
+                m_index++;
+                m_children.clear();
+            }
+
+            // TrackerBase interface
+            bool isIndexTracker() const override { return true; }
+            auto hasGenerator() const -> bool override {
+                return !!m_generator;
+            }
+            void close() override {
+                TrackerBase::close();
+                if( m_runState == CompletedSuccessfully && m_index < m_generator->size()-1 )
+                    m_runState = Executing;
+            }
+
+            // IGeneratorTracker interface
+            auto getGenerator() const -> GeneratorBasePtr const& override {
+                return m_generator;
+            }
+            void setGenerator( GeneratorBasePtr&& generator ) override {
+                m_generator = std::move( generator );
+            }
+            auto getIndex() const -> size_t override {
+                return m_index;
+            }
+        };
+        GeneratorTracker::~GeneratorTracker() {}
+    }
+
+    RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter)
+    :   m_runInfo(_config->name()),
+        m_context(getCurrentMutableContext()),
+        m_config(_config),
+        m_reporter(std::move(reporter)),
+        m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal },
+        m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions )
+    {
+        m_context.setRunner(this);
+        m_context.setConfig(m_config);
+        m_context.setResultCapture(this);
+        m_reporter->testRunStarting(m_runInfo);
+    }
+
+    RunContext::~RunContext() {
+        m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting()));
+    }
+
+    void RunContext::testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount) {
+        m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount));
+    }
+
+    void RunContext::testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount) {
+        m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting()));
+    }
+
+    Totals RunContext::runTest(TestCase const& testCase) {
+        Totals prevTotals = m_totals;
+
+        std::string redirectedCout;
+        std::string redirectedCerr;
+
+        auto const& testInfo = testCase.getTestCaseInfo();
+
+        m_reporter->testCaseStarting(testInfo);
+
+        m_activeTestCase = &testCase;
+
+        ITracker& rootTracker = m_trackerContext.startRun();
+        assert(rootTracker.isSectionTracker());
+        static_cast<SectionTracker&>(rootTracker).addInitialFilters(m_config->getSectionsToRun());
+        do {
+            m_trackerContext.startCycle();
+            m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo));
+            runCurrentTest(redirectedCout, redirectedCerr);
+        } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting());
+
+        Totals deltaTotals = m_totals.delta(prevTotals);
+        if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) {
+            deltaTotals.assertions.failed++;
+            deltaTotals.testCases.passed--;
+            deltaTotals.testCases.failed++;
+        }
+        m_totals.testCases += deltaTotals.testCases;
+        m_reporter->testCaseEnded(TestCaseStats(testInfo,
+                                  deltaTotals,
+                                  redirectedCout,
+                                  redirectedCerr,
+                                  aborting()));
+
+        m_activeTestCase = nullptr;
+        m_testCaseTracker = nullptr;
+
+        return deltaTotals;
+    }
+
+    IConfigPtr RunContext::config() const {
+        return m_config;
+    }
+
+    IStreamingReporter& RunContext::reporter() const {
+        return *m_reporter;
+    }
+
+    void RunContext::assertionEnded(AssertionResult const & result) {
+        if (result.getResultType() == ResultWas::Ok) {
+            m_totals.assertions.passed++;
+            m_lastAssertionPassed = true;
+        } else if (!result.isOk()) {
+            m_lastAssertionPassed = false;
+            if( m_activeTestCase->getTestCaseInfo().okToFail() )
+                m_totals.assertions.failedButOk++;
+            else
+                m_totals.assertions.failed++;
+        }
+        else {
+            m_lastAssertionPassed = true;
+        }
+
+        // We have no use for the return value (whether messages should be cleared), because messages were made scoped
+        // and should be let to clear themselves out.
+        static_cast<void>(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals)));
+
+        // Reset working state
+        resetAssertionInfo();
+        m_lastResult = result;
+    }
+    void RunContext::resetAssertionInfo() {
+        m_lastAssertionInfo.macroName = StringRef();
+        m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr;
+    }
+
+    bool RunContext::sectionStarted(SectionInfo const & sectionInfo, Counts & assertions) {
+        ITracker& sectionTracker = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo));
+        if (!sectionTracker.isOpen())
+            return false;
+        m_activeSections.push_back(&sectionTracker);
+
+        m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo;
+
+        m_reporter->sectionStarting(sectionInfo);
+
+        assertions = m_totals.assertions;
+
+        return true;
+    }
+    auto RunContext::acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& {
+        using namespace Generators;
+        GeneratorTracker& tracker = GeneratorTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( "generator", lineInfo ) );
+        assert( tracker.isOpen() );
+        m_lastAssertionInfo.lineInfo = lineInfo;
+        return tracker;
+    }
+
+    bool RunContext::testForMissingAssertions(Counts& assertions) {
+        if (assertions.total() != 0)
+            return false;
+        if (!m_config->warnAboutMissingAssertions())
+            return false;
+        if (m_trackerContext.currentTracker().hasChildren())
+            return false;
+        m_totals.assertions.failed++;
+        assertions.failed++;
+        return true;
+    }
+
+    void RunContext::sectionEnded(SectionEndInfo const & endInfo) {
+        Counts assertions = m_totals.assertions - endInfo.prevAssertions;
+        bool missingAssertions = testForMissingAssertions(assertions);
+
+        if (!m_activeSections.empty()) {
+            m_activeSections.back()->close();
+            m_activeSections.pop_back();
+        }
+
+        m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions));
+        m_messages.clear();
+    }
+
+    void RunContext::sectionEndedEarly(SectionEndInfo const & endInfo) {
+        if (m_unfinishedSections.empty())
+            m_activeSections.back()->fail();
+        else
+            m_activeSections.back()->close();
+        m_activeSections.pop_back();
+
+        m_unfinishedSections.push_back(endInfo);
+    }
+    void RunContext::benchmarkStarting( BenchmarkInfo const& info ) {
+        m_reporter->benchmarkStarting( info );
+    }
+    void RunContext::benchmarkEnded( BenchmarkStats const& stats ) {
+        m_reporter->benchmarkEnded( stats );
+    }
+
+    void RunContext::pushScopedMessage(MessageInfo const & message) {
+        m_messages.push_back(message);
+    }
+
+    void RunContext::popScopedMessage(MessageInfo const & message) {
+        m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end());
+    }
+
+    std::string RunContext::getCurrentTestName() const {
+        return m_activeTestCase
+            ? m_activeTestCase->getTestCaseInfo().name
+            : std::string();
+    }
+
+    const AssertionResult * RunContext::getLastResult() const {
+        return &(*m_lastResult);
+    }
+
+    void RunContext::exceptionEarlyReported() {
+        m_shouldReportUnexpected = false;
+    }
+
+    void RunContext::handleFatalErrorCondition( StringRef message ) {
+        // First notify reporter that bad things happened
+        m_reporter->fatalErrorEncountered(message);
+
+        // Don't rebuild the result -- the stringification itself can cause more fatal errors
+        // Instead, fake a result data.
+        AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } );
+        tempResult.message = message;
+        AssertionResult result(m_lastAssertionInfo, tempResult);
+
+        assertionEnded(result);
+
+        handleUnfinishedSections();
+
+        // Recreate section for test case (as we will lose the one that was in scope)
+        auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
+        SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name);
+
+        Counts assertions;
+        assertions.failed = 1;
+        SectionStats testCaseSectionStats(testCaseSection, assertions, 0, false);
+        m_reporter->sectionEnded(testCaseSectionStats);
+
+        auto const& testInfo = m_activeTestCase->getTestCaseInfo();
+
+        Totals deltaTotals;
+        deltaTotals.testCases.failed = 1;
+        deltaTotals.assertions.failed = 1;
+        m_reporter->testCaseEnded(TestCaseStats(testInfo,
+                                  deltaTotals,
+                                  std::string(),
+                                  std::string(),
+                                  false));
+        m_totals.testCases.failed++;
+        testGroupEnded(std::string(), m_totals, 1, 1);
+        m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false));
+    }
+
+    bool RunContext::lastAssertionPassed() {
+         return m_lastAssertionPassed;
+    }
+
+    void RunContext::assertionPassed() {
+        m_lastAssertionPassed = true;
+        ++m_totals.assertions.passed;
+        resetAssertionInfo();
+    }
+
+    bool RunContext::aborting() const {
+        return m_totals.assertions.failed >= static_cast<std::size_t>(m_config->abortAfter());
+    }
+
+    void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) {
+        auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
+        SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name);
+        m_reporter->sectionStarting(testCaseSection);
+        Counts prevAssertions = m_totals.assertions;
+        double duration = 0;
+        m_shouldReportUnexpected = true;
+        m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal };
+
+        seedRng(*m_config);
+
+        Timer timer;
+        CATCH_TRY {
+            if (m_reporter->getPreferences().shouldRedirectStdOut) {
+#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT)
+                RedirectedStdOut redirectedStdOut;
+                RedirectedStdErr redirectedStdErr;
+
+                timer.start();
+                invokeActiveTestCase();
+                redirectedCout += redirectedStdOut.str();
+                redirectedCerr += redirectedStdErr.str();
+#else
+                OutputRedirect r(redirectedCout, redirectedCerr);
+                timer.start();
+                invokeActiveTestCase();
+#endif
+            } else {
+                timer.start();
+                invokeActiveTestCase();
+            }
+            duration = timer.getElapsedSeconds();
+        } CATCH_CATCH_ANON (TestFailureException&) {
+            // This just means the test was aborted due to failure
+        } CATCH_CATCH_ALL {
+            // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions
+            // are reported without translation at the point of origin.
+            if( m_shouldReportUnexpected ) {
+                AssertionReaction dummyReaction;
+                handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction );
+            }
+        }
+        Counts assertions = m_totals.assertions - prevAssertions;
+        bool missingAssertions = testForMissingAssertions(assertions);
+
+        m_testCaseTracker->close();
+        handleUnfinishedSections();
+        m_messages.clear();
+
+        SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions);
+        m_reporter->sectionEnded(testCaseSectionStats);
+    }
+
+    void RunContext::invokeActiveTestCase() {
+        FatalConditionHandler fatalConditionHandler; // Handle signals
+        m_activeTestCase->invoke();
+        fatalConditionHandler.reset();
+    }
+
+    void RunContext::handleUnfinishedSections() {
+        // If sections ended prematurely due to an exception we stored their
+        // infos here so we can tear them down outside the unwind process.
+        for (auto it = m_unfinishedSections.rbegin(),
+             itEnd = m_unfinishedSections.rend();
+             it != itEnd;
+             ++it)
+            sectionEnded(*it);
+        m_unfinishedSections.clear();
+    }
+
+    void RunContext::handleExpr(
+        AssertionInfo const& info,
+        ITransientExpression const& expr,
+        AssertionReaction& reaction
+    ) {
+        m_reporter->assertionStarting( info );
+
+        bool negated = isFalseTest( info.resultDisposition );
+        bool result = expr.getResult() != negated;
+
+        if( result ) {
+            if (!m_includeSuccessfulResults) {
+                assertionPassed();
+            }
+            else {
+                reportExpr(info, ResultWas::Ok, &expr, negated);
+            }
+        }
+        else {
+            reportExpr(info, ResultWas::ExpressionFailed, &expr, negated );
+            populateReaction( reaction );
+        }
+    }
+    void RunContext::reportExpr(
+            AssertionInfo const &info,
+            ResultWas::OfType resultType,
+            ITransientExpression const *expr,
+            bool negated ) {
+
+        m_lastAssertionInfo = info;
+        AssertionResultData data( resultType, LazyExpression( negated ) );
+
+        AssertionResult assertionResult{ info, data };
+        assertionResult.m_resultData.lazyExpression.m_transientExpression = expr;
+
+        assertionEnded( assertionResult );
+    }
+
+    void RunContext::handleMessage(
+            AssertionInfo const& info,
+            ResultWas::OfType resultType,
+            StringRef const& message,
+            AssertionReaction& reaction
+    ) {
+        m_reporter->assertionStarting( info );
+
+        m_lastAssertionInfo = info;
+
+        AssertionResultData data( resultType, LazyExpression( false ) );
+        data.message = message;
+        AssertionResult assertionResult{ m_lastAssertionInfo, data };
+        assertionEnded( assertionResult );
+        if( !assertionResult.isOk() )
+            populateReaction( reaction );
+    }
+    void RunContext::handleUnexpectedExceptionNotThrown(
+            AssertionInfo const& info,
+            AssertionReaction& reaction
+    ) {
+        handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction);
+    }
+
+    void RunContext::handleUnexpectedInflightException(
+            AssertionInfo const& info,
+            std::string const& message,
+            AssertionReaction& reaction
+    ) {
+        m_lastAssertionInfo = info;
+
+        AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) );
+        data.message = message;
+        AssertionResult assertionResult{ info, data };
+        assertionEnded( assertionResult );
+        populateReaction( reaction );
+    }
+
+    void RunContext::populateReaction( AssertionReaction& reaction ) {
+        reaction.shouldDebugBreak = m_config->shouldDebugBreak();
+        reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal);
+    }
+
+    void RunContext::handleIncomplete(
+            AssertionInfo const& info
+    ) {
+        m_lastAssertionInfo = info;
+
+        AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) );
+        data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE";
+        AssertionResult assertionResult{ info, data };
+        assertionEnded( assertionResult );
+    }
+    void RunContext::handleNonExpr(
+            AssertionInfo const &info,
+            ResultWas::OfType resultType,
+            AssertionReaction &reaction
+    ) {
+        m_lastAssertionInfo = info;
+
+        AssertionResultData data( resultType, LazyExpression( false ) );
+        AssertionResult assertionResult{ info, data };
+        assertionEnded( assertionResult );
+
+        if( !assertionResult.isOk() )
+            populateReaction( reaction );
+    }
+
+    IResultCapture& getResultCapture() {
+        if (auto* capture = getCurrentContext().getResultCapture())
+            return *capture;
+        else
+            CATCH_INTERNAL_ERROR("No result capture instance");
+    }
+}
+// end catch_run_context.cpp
+// start catch_section.cpp
+
+namespace Catch {
+
+    Section::Section( SectionInfo const& info )
+    :   m_info( info ),
+        m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) )
+    {
+        m_timer.start();
+    }
+
+    Section::~Section() {
+        if( m_sectionIncluded ) {
+            SectionEndInfo endInfo{ m_info, m_assertions, m_timer.getElapsedSeconds() };
+            if( uncaught_exceptions() )
+                getResultCapture().sectionEndedEarly( endInfo );
+            else
+                getResultCapture().sectionEnded( endInfo );
+        }
+    }
+
+    // This indicates whether the section should be executed or not
+    Section::operator bool() const {
+        return m_sectionIncluded;
+    }
+
+} // end namespace Catch
+// end catch_section.cpp
+// start catch_section_info.cpp
+
+namespace Catch {
+
+    SectionInfo::SectionInfo
+        (   SourceLineInfo const& _lineInfo,
+            std::string const& _name )
+    :   name( _name ),
+        lineInfo( _lineInfo )
+    {}
+
+} // end namespace Catch
+// end catch_section_info.cpp
+// start catch_session.cpp
+
+// start catch_session.h
+
+#include <memory>
+
+namespace Catch {
+
+    class Session : NonCopyable {
+    public:
+
+        Session();
+        ~Session() override;
+
+        void showHelp() const;
+        void libIdentify();
+
+        int applyCommandLine( int argc, char const * const * argv );
+    #if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE)
+        int applyCommandLine( int argc, wchar_t const * const * argv );
+    #endif
+
+        void useConfigData( ConfigData const& configData );
+
+        template<typename CharT>
+        int run(int argc, CharT const * const argv[]) {
+            if (m_startupExceptions)
+                return 1;
+            int returnCode = applyCommandLine(argc, argv);
+            if (returnCode == 0)
+                returnCode = run();
+            return returnCode;
+        }
+
+        int run();
+
+        clara::Parser const& cli() const;
+        void cli( clara::Parser const& newParser );
+        ConfigData& configData();
+        Config& config();
+    private:
+        int runInternal();
+
+        clara::Parser m_cli;
+        ConfigData m_configData;
+        std::shared_ptr<Config> m_config;
+        bool m_startupExceptions = false;
+    };
+
+} // end namespace Catch
+
+// end catch_session.h
+// start catch_version.h
+
+#include <iosfwd>
+
+namespace Catch {
+
+    // Versioning information
+    struct Version {
+        Version( Version const& ) = delete;
+        Version& operator=( Version const& ) = delete;
+        Version(    unsigned int _majorVersion,
+                    unsigned int _minorVersion,
+                    unsigned int _patchNumber,
+                    char const * const _branchName,
+                    unsigned int _buildNumber );
+
+        unsigned int const majorVersion;
+        unsigned int const minorVersion;
+        unsigned int const patchNumber;
+
+        // buildNumber is only used if branchName is not null
+        char const * const branchName;
+        unsigned int const buildNumber;
+
+        friend std::ostream& operator << ( std::ostream& os, Version const& version );
+    };
+
+    Version const& libraryVersion();
+}
+
+// end catch_version.h
+#include <cstdlib>
+#include <iomanip>
+
+namespace Catch {
+
+    namespace {
+        const int MaxExitCode = 255;
+
+        IStreamingReporterPtr createReporter(std::string const& reporterName, IConfigPtr const& config) {
+            auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config);
+            CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'");
+
+            return reporter;
+        }
+
+        IStreamingReporterPtr makeReporter(std::shared_ptr<Config> const& config) {
+            if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) {
+                return createReporter(config->getReporterName(), config);
+            }
+
+            auto multi = std::unique_ptr<ListeningReporter>(new ListeningReporter);
+
+            auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners();
+            for (auto const& listener : listeners) {
+                multi->addListener(listener->create(Catch::ReporterConfig(config)));
+            }
+            multi->addReporter(createReporter(config->getReporterName(), config));
+            return std::move(multi);
+        }
+
+        Catch::Totals runTests(std::shared_ptr<Config> const& config) {
+            auto reporter = makeReporter(config);
+
+            RunContext context(config, std::move(reporter));
+
+            Totals totals;
+
+            context.testGroupStarting(config->name(), 1, 1);
+
+            TestSpec testSpec = config->testSpec();
+
+            auto const& allTestCases = getAllTestCasesSorted(*config);
+            for (auto const& testCase : allTestCases) {
+                if (!context.aborting() && matchTest(testCase, testSpec, *config))
+                    totals += context.runTest(testCase);
+                else
+                    context.reporter().skipTest(testCase);
+            }
+
+            if (config->warnAboutNoTests() && totals.testCases.total() == 0) {
+                ReusableStringStream testConfig;
+
+                bool first = true;
+                for (const auto& input : config->getTestsOrTags()) {
+                    if (!first) { testConfig << ' '; }
+                    first = false;
+                    testConfig << input;
+                }
+
+                context.reporter().noMatchingTestCases(testConfig.str());
+                totals.error = -1;
+            }
+
+            context.testGroupEnded(config->name(), totals, 1, 1);
+            return totals;
+        }
+
+        void applyFilenamesAsTags(Catch::IConfig const& config) {
+            auto& tests = const_cast<std::vector<TestCase>&>(getAllTestCasesSorted(config));
+            for (auto& testCase : tests) {
+                auto tags = testCase.tags;
+
+                std::string filename = testCase.lineInfo.file;
+                auto lastSlash = filename.find_last_of("\\/");
+                if (lastSlash != std::string::npos) {
+                    filename.erase(0, lastSlash);
+                    filename[0] = '#';
+                }
+
+                auto lastDot = filename.find_last_of('.');
+                if (lastDot != std::string::npos) {
+                    filename.erase(lastDot);
+                }
+
+                tags.push_back(std::move(filename));
+                setTags(testCase, tags);
+            }
+        }
+
+    } // anon namespace
+
+    Session::Session() {
+        static bool alreadyInstantiated = false;
+        if( alreadyInstantiated ) {
+            CATCH_TRY { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); }
+            CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); }
+        }
+
+        // There cannot be exceptions at startup in no-exception mode.
+#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
+        const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions();
+        if ( !exceptions.empty() ) {
+            m_startupExceptions = true;
+            Colour colourGuard( Colour::Red );
+            Catch::cerr() << "Errors occurred during startup!" << '\n';
+            // iterate over all exceptions and notify user
+            for ( const auto& ex_ptr : exceptions ) {
+                try {
+                    std::rethrow_exception(ex_ptr);
+                } catch ( std::exception const& ex ) {
+                    Catch::cerr() << Column( ex.what() ).indent(2) << '\n';
+                }
+            }
+        }
+#endif
+
+        alreadyInstantiated = true;
+        m_cli = makeCommandLineParser( m_configData );
+    }
+    Session::~Session() {
+        Catch::cleanUp();
+    }
+
+    void Session::showHelp() const {
+        Catch::cout()
+                << "\nCatch v" << libraryVersion() << "\n"
+                << m_cli << std::endl
+                << "For more detailed usage please see the project docs\n" << std::endl;
+    }
+    void Session::libIdentify() {
+        Catch::cout()
+                << std::left << std::setw(16) << "description: " << "A Catch test executable\n"
+                << std::left << std::setw(16) << "category: " << "testframework\n"
+                << std::left << std::setw(16) << "framework: " << "Catch Test\n"
+                << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl;
+    }
+
+    int Session::applyCommandLine( int argc, char const * const * argv ) {
+        if( m_startupExceptions )
+            return 1;
+
+        auto result = m_cli.parse( clara::Args( argc, argv ) );
+        if( !result ) {
+            Catch::cerr()
+                << Colour( Colour::Red )
+                << "\nError(s) in input:\n"
+                << Column( result.errorMessage() ).indent( 2 )
+                << "\n\n";
+            Catch::cerr() << "Run with -? for usage\n" << std::endl;
+            return MaxExitCode;
+        }
+
+        if( m_configData.showHelp )
+            showHelp();
+        if( m_configData.libIdentify )
+            libIdentify();
+        m_config.reset();
+        return 0;
+    }
+
+#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE)
+    int Session::applyCommandLine( int argc, wchar_t const * const * argv ) {
+
+        char **utf8Argv = new char *[ argc ];
+
+        for ( int i = 0; i < argc; ++i ) {
+            int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL );
+
+            utf8Argv[ i ] = new char[ bufSize ];
+
+            WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL );
+        }
+
+        int returnCode = applyCommandLine( argc, utf8Argv );
+
+        for ( int i = 0; i < argc; ++i )
+            delete [] utf8Argv[ i ];
+
+        delete [] utf8Argv;
+
+        return returnCode;
+    }
+#endif
+
+    void Session::useConfigData( ConfigData const& configData ) {
+        m_configData = configData;
+        m_config.reset();
+    }
+
+    int Session::run() {
+        if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) {
+            Catch::cout() << "...waiting for enter/ return before starting" << std::endl;
+            static_cast<void>(std::getchar());
+        }
+        int exitCode = runInternal();
+        if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) {
+            Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl;
+            static_cast<void>(std::getchar());
+        }
+        return exitCode;
+    }
+
+    clara::Parser const& Session::cli() const {
+        return m_cli;
+    }
+    void Session::cli( clara::Parser const& newParser ) {
+        m_cli = newParser;
+    }
+    ConfigData& Session::configData() {
+        return m_configData;
+    }
+    Config& Session::config() {
+        if( !m_config )
+            m_config = std::make_shared<Config>( m_configData );
+        return *m_config;
+    }
+
+    int Session::runInternal() {
+        if( m_startupExceptions )
+            return 1;
+
+        if (m_configData.showHelp || m_configData.libIdentify) {
+            return 0;
+        }
+
+        CATCH_TRY {
+            config(); // Force config to be constructed
+
+            seedRng( *m_config );
+
+            if( m_configData.filenamesAsTags )
+                applyFilenamesAsTags( *m_config );
+
+            // Handle list request
+            if( Option<std::size_t> listed = list( config() ) )
+                return static_cast<int>( *listed );
+
+            auto totals = runTests( m_config );
+            // Note that on unices only the lower 8 bits are usually used, clamping
+            // the return value to 255 prevents false negative when some multiple
+            // of 256 tests has failed
+            return (std::min) (MaxExitCode, (std::max) (totals.error, static_cast<int>(totals.assertions.failed)));
+        }
+#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
+        catch( std::exception& ex ) {
+            Catch::cerr() << ex.what() << std::endl;
+            return MaxExitCode;
+        }
+#endif
+    }
+
+} // end namespace Catch
+// end catch_session.cpp
+// start catch_singletons.cpp
+
+#include <vector>
+
+namespace Catch {
+
+    namespace {
+        static auto getSingletons() -> std::vector<ISingleton*>*& {
+            static std::vector<ISingleton*>* g_singletons = nullptr;
+            if( !g_singletons )
+                g_singletons = new std::vector<ISingleton*>();
+            return g_singletons;
+        }
+    }
+
+    ISingleton::~ISingleton() {}
+
+    void addSingleton(ISingleton* singleton ) {
+        getSingletons()->push_back( singleton );
+    }
+    void cleanupSingletons() {
+        auto& singletons = getSingletons();
+        for( auto singleton : *singletons )
+            delete singleton;
+        delete singletons;
+        singletons = nullptr;
+    }
+
+} // namespace Catch
+// end catch_singletons.cpp
+// start catch_startup_exception_registry.cpp
+
+namespace Catch {
+void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept {
+        CATCH_TRY {
+            m_exceptions.push_back(exception);
+        } CATCH_CATCH_ALL {
+            // If we run out of memory during start-up there's really not a lot more we can do about it
+            std::terminate();
+        }
+    }
+
+    std::vector<std::exception_ptr> const& StartupExceptionRegistry::getExceptions() const noexcept {
+        return m_exceptions;
+    }
+
+} // end namespace Catch
+// end catch_startup_exception_registry.cpp
+// start catch_stream.cpp
+
+#include <cstdio>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <vector>
+#include <memory>
+
+namespace Catch {
+
+    Catch::IStream::~IStream() = default;
+
+    namespace detail { namespace {
+        template<typename WriterF, std::size_t bufferSize=256>
+        class StreamBufImpl : public std::streambuf {
+            char data[bufferSize];
+            WriterF m_writer;
+
+        public:
+            StreamBufImpl() {
+                setp( data, data + sizeof(data) );
+            }
+
+            ~StreamBufImpl() noexcept {
+                StreamBufImpl::sync();
+            }
+
+        private:
+            int overflow( int c ) override {
+                sync();
+
+                if( c != EOF ) {
+                    if( pbase() == epptr() )
+                        m_writer( std::string( 1, static_cast<char>( c ) ) );
+                    else
+                        sputc( static_cast<char>( c ) );
+                }
+                return 0;
+            }
+
+            int sync() override {
+                if( pbase() != pptr() ) {
+                    m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) );
+                    setp( pbase(), epptr() );
+                }
+                return 0;
+            }
+        };
+
+        ///////////////////////////////////////////////////////////////////////////
+
+        struct OutputDebugWriter {
+
+            void operator()( std::string const&str ) {
+                writeToDebugConsole( str );
+            }
+        };
+
+        ///////////////////////////////////////////////////////////////////////////
+
+        class FileStream : public IStream {
+            mutable std::ofstream m_ofs;
+        public:
+            FileStream( StringRef filename ) {
+                m_ofs.open( filename.c_str() );
+                CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << "'" );
+            }
+            ~FileStream() override = default;
+        public: // IStream
+            std::ostream& stream() const override {
+                return m_ofs;
+            }
+        };
+
+        ///////////////////////////////////////////////////////////////////////////
+
+        class CoutStream : public IStream {
+            mutable std::ostream m_os;
+        public:
+            // Store the streambuf from cout up-front because
+            // cout may get redirected when running tests
+            CoutStream() : m_os( Catch::cout().rdbuf() ) {}
+            ~CoutStream() override = default;
+
+        public: // IStream
+            std::ostream& stream() const override { return m_os; }
+        };
+
+        ///////////////////////////////////////////////////////////////////////////
+
+        class DebugOutStream : public IStream {
+            std::unique_ptr<StreamBufImpl<OutputDebugWriter>> m_streamBuf;
+            mutable std::ostream m_os;
+        public:
+            DebugOutStream()
+            :   m_streamBuf( new StreamBufImpl<OutputDebugWriter>() ),
+                m_os( m_streamBuf.get() )
+            {}
+
+            ~DebugOutStream() override = default;
+
+        public: // IStream
+            std::ostream& stream() const override { return m_os; }
+        };
+
+    }} // namespace anon::detail
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    auto makeStream( StringRef const &filename ) -> IStream const* {
+        if( filename.empty() )
+            return new detail::CoutStream();
+        else if( filename[0] == '%' ) {
+            if( filename == "%debug" )
+                return new detail::DebugOutStream();
+            else
+                CATCH_ERROR( "Unrecognised stream: '" << filename << "'" );
+        }
+        else
+            return new detail::FileStream( filename );
+    }
+
+    // This class encapsulates the idea of a pool of ostringstreams that can be reused.
+    struct StringStreams {
+        std::vector<std::unique_ptr<std::ostringstream>> m_streams;
+        std::vector<std::size_t> m_unused;
+        std::ostringstream m_referenceStream; // Used for copy state/ flags from
+
+        auto add() -> std::size_t {
+            if( m_unused.empty() ) {
+                m_streams.push_back( std::unique_ptr<std::ostringstream>( new std::ostringstream ) );
+                return m_streams.size()-1;
+            }
+            else {
+                auto index = m_unused.back();
+                m_unused.pop_back();
+                return index;
+            }
+        }
+
+        void release( std::size_t index ) {
+            m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state
+            m_unused.push_back(index);
+        }
+    };
+
+    ReusableStringStream::ReusableStringStream()
+    :   m_index( Singleton<StringStreams>::getMutable().add() ),
+        m_oss( Singleton<StringStreams>::getMutable().m_streams[m_index].get() )
+    {}
+
+    ReusableStringStream::~ReusableStringStream() {
+        static_cast<std::ostringstream*>( m_oss )->str("");
+        m_oss->clear();
+        Singleton<StringStreams>::getMutable().release( m_index );
+    }
+
+    auto ReusableStringStream::str() const -> std::string {
+        return static_cast<std::ostringstream*>( m_oss )->str();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+
+#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions
+    std::ostream& cout() { return std::cout; }
+    std::ostream& cerr() { return std::cerr; }
+    std::ostream& clog() { return std::clog; }
+#endif
+}
+// end catch_stream.cpp
+// start catch_string_manip.cpp
+
+#include <algorithm>
+#include <ostream>
+#include <cstring>
+#include <cctype>
+
+namespace Catch {
+
+    namespace {
+        char toLowerCh(char c) {
+            return static_cast<char>( std::tolower( c ) );
+        }
+    }
+
+    bool startsWith( std::string const& s, std::string const& prefix ) {
+        return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin());
+    }
+    bool startsWith( std::string const& s, char prefix ) {
+        return !s.empty() && s[0] == prefix;
+    }
+    bool endsWith( std::string const& s, std::string const& suffix ) {
+        return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin());
+    }
+    bool endsWith( std::string const& s, char suffix ) {
+        return !s.empty() && s[s.size()-1] == suffix;
+    }
+    bool contains( std::string const& s, std::string const& infix ) {
+        return s.find( infix ) != std::string::npos;
+    }
+    void toLowerInPlace( std::string& s ) {
+        std::transform( s.begin(), s.end(), s.begin(), toLowerCh );
+    }
+    std::string toLower( std::string const& s ) {
+        std::string lc = s;
+        toLowerInPlace( lc );
+        return lc;
+    }
+    std::string trim( std::string const& str ) {
+        static char const* whitespaceChars = "\n\r\t ";
+        std::string::size_type start = str.find_first_not_of( whitespaceChars );
+        std::string::size_type end = str.find_last_not_of( whitespaceChars );
+
+        return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string();
+    }
+
+    bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) {
+        bool replaced = false;
+        std::size_t i = str.find( replaceThis );
+        while( i != std::string::npos ) {
+            replaced = true;
+            str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() );
+            if( i < str.size()-withThis.size() )
+                i = str.find( replaceThis, i+withThis.size() );
+            else
+                i = std::string::npos;
+        }
+        return replaced;
+    }
+
+    pluralise::pluralise( std::size_t count, std::string const& label )
+    :   m_count( count ),
+        m_label( label )
+    {}
+
+    std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) {
+        os << pluraliser.m_count << ' ' << pluraliser.m_label;
+        if( pluraliser.m_count != 1 )
+            os << 's';
+        return os;
+    }
+
+}
+// end catch_string_manip.cpp
+// start catch_stringref.cpp
+
+#if defined(__clang__)
+#    pragma clang diagnostic push
+#    pragma clang diagnostic ignored "-Wexit-time-destructors"
+#endif
+
+#include <ostream>
+#include <cstring>
+#include <cstdint>
+
+namespace {
+    const uint32_t byte_2_lead = 0xC0;
+    const uint32_t byte_3_lead = 0xE0;
+    const uint32_t byte_4_lead = 0xF0;
+}
+
+namespace Catch {
+    StringRef::StringRef( char const* rawChars ) noexcept
+    : StringRef( rawChars, static_cast<StringRef::size_type>(std::strlen(rawChars) ) )
+    {}
+
+    StringRef::operator std::string() const {
+        return std::string( m_start, m_size );
+    }
+
+    void StringRef::swap( StringRef& other ) noexcept {
+        std::swap( m_start, other.m_start );
+        std::swap( m_size, other.m_size );
+        std::swap( m_data, other.m_data );
+    }
+
+    auto StringRef::c_str() const -> char const* {
+        if( isSubstring() )
+           const_cast<StringRef*>( this )->takeOwnership();
+        return m_start;
+    }
+    auto StringRef::currentData() const noexcept -> char const* {
+        return m_start;
+    }
+
+    auto StringRef::isOwned() const noexcept -> bool {
+        return m_data != nullptr;
+    }
+    auto StringRef::isSubstring() const noexcept -> bool {
+        return m_start[m_size] != '\0';
+    }
+
+    void StringRef::takeOwnership() {
+        if( !isOwned() ) {
+            m_data = new char[m_size+1];
+            memcpy( m_data, m_start, m_size );
+            m_data[m_size] = '\0';
+            m_start = m_data;
+        }
+    }
+    auto StringRef::substr( size_type start, size_type size ) const noexcept -> StringRef {
+        if( start < m_size )
+            return StringRef( m_start+start, size );
+        else
+            return StringRef();
+    }
+    auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool {
+        return
+            size() == other.size() &&
+            (std::strncmp( m_start, other.m_start, size() ) == 0);
+    }
+    auto StringRef::operator != ( StringRef const& other ) const noexcept -> bool {
+        return !operator==( other );
+    }
+
+    auto StringRef::operator[](size_type index) const noexcept -> char {
+        return m_start[index];
+    }
+
+    auto StringRef::numberOfCharacters() const noexcept -> size_type {
+        size_type noChars = m_size;
+        // Make adjustments for uft encodings
+        for( size_type i=0; i < m_size; ++i ) {
+            char c = m_start[i];
+            if( ( c & byte_2_lead ) == byte_2_lead ) {
+                noChars--;
+                if (( c & byte_3_lead ) == byte_3_lead )
+                    noChars--;
+                if( ( c & byte_4_lead ) == byte_4_lead )
+                    noChars--;
+            }
+        }
+        return noChars;
+    }
+
+    auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string {
+        std::string str;
+        str.reserve( lhs.size() + rhs.size() );
+        str += lhs;
+        str += rhs;
+        return str;
+    }
+    auto operator + ( StringRef const& lhs, const char* rhs ) -> std::string {
+        return std::string( lhs ) + std::string( rhs );
+    }
+    auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string {
+        return std::string( lhs ) + std::string( rhs );
+    }
+
+    auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& {
+        return os.write(str.currentData(), str.size());
+    }
+
+    auto operator+=( std::string& lhs, StringRef const& rhs ) -> std::string& {
+        lhs.append(rhs.currentData(), rhs.size());
+        return lhs;
+    }
+
+} // namespace Catch
+
+#if defined(__clang__)
+#    pragma clang diagnostic pop
+#endif
+// end catch_stringref.cpp
+// start catch_tag_alias.cpp
+
+namespace Catch {
+    TagAlias::TagAlias(std::string const & _tag, SourceLineInfo _lineInfo): tag(_tag), lineInfo(_lineInfo) {}
+}
+// end catch_tag_alias.cpp
+// start catch_tag_alias_autoregistrar.cpp
+
+namespace Catch {
+
+    RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) {
+        CATCH_TRY {
+            getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo);
+        } CATCH_CATCH_ALL {
+            // Do not throw when constructing global objects, instead register the exception to be processed later
+            getMutableRegistryHub().registerStartupException();
+        }
+    }
+
+}
+// end catch_tag_alias_autoregistrar.cpp
+// start catch_tag_alias_registry.cpp
+
+#include <sstream>
+
+namespace Catch {
+
+    TagAliasRegistry::~TagAliasRegistry() {}
+
+    TagAlias const* TagAliasRegistry::find( std::string const& alias ) const {
+        auto it = m_registry.find( alias );
+        if( it != m_registry.end() )
+            return &(it->second);
+        else
+            return nullptr;
+    }
+
+    std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const {
+        std::string expandedTestSpec = unexpandedTestSpec;
+        for( auto const& registryKvp : m_registry ) {
+            std::size_t pos = expandedTestSpec.find( registryKvp.first );
+            if( pos != std::string::npos ) {
+                expandedTestSpec =  expandedTestSpec.substr( 0, pos ) +
+                                    registryKvp.second.tag +
+                                    expandedTestSpec.substr( pos + registryKvp.first.size() );
+            }
+        }
+        return expandedTestSpec;
+    }
+
+    void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) {
+        CATCH_ENFORCE( startsWith(alias, "[@") && endsWith(alias, ']'),
+                      "error: tag alias, '" << alias << "' is not of the form [@alias name].\n" << lineInfo );
+
+        CATCH_ENFORCE( m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second,
+                      "error: tag alias, '" << alias << "' already registered.\n"
+                      << "\tFirst seen at: " << find(alias)->lineInfo << "\n"
+                      << "\tRedefined at: " << lineInfo );
+    }
+
+    ITagAliasRegistry::~ITagAliasRegistry() {}
+
+    ITagAliasRegistry const& ITagAliasRegistry::get() {
+        return getRegistryHub().getTagAliasRegistry();
+    }
+
+} // end namespace Catch
+// end catch_tag_alias_registry.cpp
+// start catch_test_case_info.cpp
+
+#include <cctype>
+#include <exception>
+#include <algorithm>
+#include <sstream>
+
+namespace Catch {
+
+    namespace {
+        TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) {
+            if( startsWith( tag, '.' ) ||
+                tag == "!hide" )
+                return TestCaseInfo::IsHidden;
+            else if( tag == "!throws" )
+                return TestCaseInfo::Throws;
+            else if( tag == "!shouldfail" )
+                return TestCaseInfo::ShouldFail;
+            else if( tag == "!mayfail" )
+                return TestCaseInfo::MayFail;
+            else if( tag == "!nonportable" )
+                return TestCaseInfo::NonPortable;
+            else if( tag == "!benchmark" )
+                return static_cast<TestCaseInfo::SpecialProperties>( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden );
+            else
+                return TestCaseInfo::None;
+        }
+        bool isReservedTag( std::string const& tag ) {
+            return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( static_cast<unsigned char>(tag[0]) );
+        }
+        void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) {
+            CATCH_ENFORCE( !isReservedTag(tag),
+                          "Tag name: [" << tag << "] is not allowed.\n"
+                          << "Tag names starting with non alpha-numeric characters are reserved\n"
+                          << _lineInfo );
+        }
+    }
+
+    TestCase makeTestCase(  ITestInvoker* _testCase,
+                            std::string const& _className,
+                            NameAndTags const& nameAndTags,
+                            SourceLineInfo const& _lineInfo )
+    {
+        bool isHidden = false;
+
+        // Parse out tags
+        std::vector<std::string> tags;
+        std::string desc, tag;
+        bool inTag = false;
+        std::string _descOrTags = nameAndTags.tags;
+        for (char c : _descOrTags) {
+            if( !inTag ) {
+                if( c == '[' )
+                    inTag = true;
+                else
+                    desc += c;
+            }
+            else {
+                if( c == ']' ) {
+                    TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag );
+                    if( ( prop & TestCaseInfo::IsHidden ) != 0 )
+                        isHidden = true;
+                    else if( prop == TestCaseInfo::None )
+                        enforceNotReservedTag( tag, _lineInfo );
+
+                    tags.push_back( tag );
+                    tag.clear();
+                    inTag = false;
+                }
+                else
+                    tag += c;
+            }
+        }
+        if( isHidden ) {
+            tags.push_back( "." );
+        }
+
+        TestCaseInfo info( nameAndTags.name, _className, desc, tags, _lineInfo );
+        return TestCase( _testCase, std::move(info) );
+    }
+
+    void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags ) {
+        std::sort(begin(tags), end(tags));
+        tags.erase(std::unique(begin(tags), end(tags)), end(tags));
+        testCaseInfo.lcaseTags.clear();
+
+        for( auto const& tag : tags ) {
+            std::string lcaseTag = toLower( tag );
+            testCaseInfo.properties = static_cast<TestCaseInfo::SpecialProperties>( testCaseInfo.properties | parseSpecialTag( lcaseTag ) );
+            testCaseInfo.lcaseTags.push_back( lcaseTag );
+        }
+        testCaseInfo.tags = std::move(tags);
+    }
+
+    TestCaseInfo::TestCaseInfo( std::string const& _name,
+                                std::string const& _className,
+                                std::string const& _description,
+                                std::vector<std::string> const& _tags,
+                                SourceLineInfo const& _lineInfo )
+    :   name( _name ),
+        className( _className ),
+        description( _description ),
+        lineInfo( _lineInfo ),
+        properties( None )
+    {
+        setTags( *this, _tags );
+    }
+
+    bool TestCaseInfo::isHidden() const {
+        return ( properties & IsHidden ) != 0;
+    }
+    bool TestCaseInfo::throws() const {
+        return ( properties & Throws ) != 0;
+    }
+    bool TestCaseInfo::okToFail() const {
+        return ( properties & (ShouldFail | MayFail ) ) != 0;
+    }
+    bool TestCaseInfo::expectedToFail() const {
+        return ( properties & (ShouldFail ) ) != 0;
+    }
+
+    std::string TestCaseInfo::tagsAsString() const {
+        std::string ret;
+        // '[' and ']' per tag
+        std::size_t full_size = 2 * tags.size();
+        for (const auto& tag : tags) {
+            full_size += tag.size();
+        }
+        ret.reserve(full_size);
+        for (const auto& tag : tags) {
+            ret.push_back('[');
+            ret.append(tag);
+            ret.push_back(']');
+        }
+
+        return ret;
+    }
+
+    TestCase::TestCase( ITestInvoker* testCase, TestCaseInfo&& info ) : TestCaseInfo( std::move(info) ), test( testCase ) {}
+
+    TestCase TestCase::withName( std::string const& _newName ) const {
+        TestCase other( *this );
+        other.name = _newName;
+        return other;
+    }
+
+    void TestCase::invoke() const {
+        test->invoke();
+    }
+
+    bool TestCase::operator == ( TestCase const& other ) const {
+        return  test.get() == other.test.get() &&
+                name == other.name &&
+                className == other.className;
+    }
+
+    bool TestCase::operator < ( TestCase const& other ) const {
+        return name < other.name;
+    }
+
+    TestCaseInfo const& TestCase::getTestCaseInfo() const
+    {
+        return *this;
+    }
+
+} // end namespace Catch
+// end catch_test_case_info.cpp
+// start catch_test_case_registry_impl.cpp
+
+#include <sstream>
+
+namespace Catch {
+
+    std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ) {
+
+        std::vector<TestCase> sorted = unsortedTestCases;
+
+        switch( config.runOrder() ) {
+            case RunTests::InLexicographicalOrder:
+                std::sort( sorted.begin(), sorted.end() );
+                break;
+            case RunTests::InRandomOrder:
+                seedRng( config );
+                std::shuffle( sorted.begin(), sorted.end(), rng() );
+                break;
+            case RunTests::InDeclarationOrder:
+                // already in declaration order
+                break;
+        }
+        return sorted;
+    }
+    bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) {
+        return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() );
+    }
+
+    void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ) {
+        std::set<TestCase> seenFunctions;
+        for( auto const& function : functions ) {
+            auto prev = seenFunctions.insert( function );
+            CATCH_ENFORCE( prev.second,
+                    "error: TEST_CASE( \"" << function.name << "\" ) already defined.\n"
+                    << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n"
+                    << "\tRedefined at " << function.getTestCaseInfo().lineInfo );
+        }
+    }
+
+    std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ) {
+        std::vector<TestCase> filtered;
+        filtered.reserve( testCases.size() );
+        for( auto const& testCase : testCases )
+            if( matchTest( testCase, testSpec, config ) )
+                filtered.push_back( testCase );
+        return filtered;
+    }
+    std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ) {
+        return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config );
+    }
+
+    void TestRegistry::registerTest( TestCase const& testCase ) {
+        std::string name = testCase.getTestCaseInfo().name;
+        if( name.empty() ) {
+            ReusableStringStream rss;
+            rss << "Anonymous test case " << ++m_unnamedCount;
+            return registerTest( testCase.withName( rss.str() ) );
+        }
+        m_functions.push_back( testCase );
+    }
+
+    std::vector<TestCase> const& TestRegistry::getAllTests() const {
+        return m_functions;
+    }
+    std::vector<TestCase> const& TestRegistry::getAllTestsSorted( IConfig const& config ) const {
+        if( m_sortedFunctions.empty() )
+            enforceNoDuplicateTestCases( m_functions );
+
+        if(  m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) {
+            m_sortedFunctions = sortTests( config, m_functions );
+            m_currentSortOrder = config.runOrder();
+        }
+        return m_sortedFunctions;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    TestInvokerAsFunction::TestInvokerAsFunction( void(*testAsFunction)() ) noexcept : m_testAsFunction( testAsFunction ) {}
+
+    void TestInvokerAsFunction::invoke() const {
+        m_testAsFunction();
+    }
+
+    std::string extractClassName( StringRef const& classOrQualifiedMethodName ) {
+        std::string className = classOrQualifiedMethodName;
+        if( startsWith( className, '&' ) )
+        {
+            std::size_t lastColons = className.rfind( "::" );
+            std::size_t penultimateColons = className.rfind( "::", lastColons-1 );
+            if( penultimateColons == std::string::npos )
+                penultimateColons = 1;
+            className = className.substr( penultimateColons, lastColons-penultimateColons );
+        }
+        return className;
+    }
+
+} // end namespace Catch
+// end catch_test_case_registry_impl.cpp
+// start catch_test_case_tracker.cpp
+
+#include <algorithm>
+#include <cassert>
+#include <stdexcept>
+#include <memory>
+#include <sstream>
+
+#if defined(__clang__)
+#    pragma clang diagnostic push
+#    pragma clang diagnostic ignored "-Wexit-time-destructors"
+#endif
+
+namespace Catch {
+namespace TestCaseTracking {
+
+    NameAndLocation::NameAndLocation( std::string const& _name, SourceLineInfo const& _location )
+    :   name( _name ),
+        location( _location )
+    {}
+
+    ITracker::~ITracker() = default;
+
+    TrackerContext& TrackerContext::instance() {
+        static TrackerContext s_instance;
+        return s_instance;
+    }
+
+    ITracker& TrackerContext::startRun() {
+        m_rootTracker = std::make_shared<SectionTracker>( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, nullptr );
+        m_currentTracker = nullptr;
+        m_runState = Executing;
+        return *m_rootTracker;
+    }
+
+    void TrackerContext::endRun() {
+        m_rootTracker.reset();
+        m_currentTracker = nullptr;
+        m_runState = NotStarted;
+    }
+
+    void TrackerContext::startCycle() {
+        m_currentTracker = m_rootTracker.get();
+        m_runState = Executing;
+    }
+    void TrackerContext::completeCycle() {
+        m_runState = CompletedCycle;
+    }
+
+    bool TrackerContext::completedCycle() const {
+        return m_runState == CompletedCycle;
+    }
+    ITracker& TrackerContext::currentTracker() {
+        return *m_currentTracker;
+    }
+    void TrackerContext::setCurrentTracker( ITracker* tracker ) {
+        m_currentTracker = tracker;
+    }
+
+    TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )
+    :   m_nameAndLocation( nameAndLocation ),
+        m_ctx( ctx ),
+        m_parent( parent )
+    {}
+
+    NameAndLocation const& TrackerBase::nameAndLocation() const {
+        return m_nameAndLocation;
+    }
+    bool TrackerBase::isComplete() const {
+        return m_runState == CompletedSuccessfully || m_runState == Failed;
+    }
+    bool TrackerBase::isSuccessfullyCompleted() const {
+        return m_runState == CompletedSuccessfully;
+    }
+    bool TrackerBase::isOpen() const {
+        return m_runState != NotStarted && !isComplete();
+    }
+    bool TrackerBase::hasChildren() const {
+        return !m_children.empty();
+    }
+
+    void TrackerBase::addChild( ITrackerPtr const& child ) {
+        m_children.push_back( child );
+    }
+
+    ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) {
+        auto it = std::find_if( m_children.begin(), m_children.end(),
+            [&nameAndLocation]( ITrackerPtr const& tracker ){
+                return
+                    tracker->nameAndLocation().location == nameAndLocation.location &&
+                    tracker->nameAndLocation().name == nameAndLocation.name;
+            } );
+        return( it != m_children.end() )
+            ? *it
+            : nullptr;
+    }
+    ITracker& TrackerBase::parent() {
+        assert( m_parent ); // Should always be non-null except for root
+        return *m_parent;
+    }
+
+    void TrackerBase::openChild() {
+        if( m_runState != ExecutingChildren ) {
+            m_runState = ExecutingChildren;
+            if( m_parent )
+                m_parent->openChild();
+        }
+    }
+
+    bool TrackerBase::isSectionTracker() const { return false; }
+    bool TrackerBase::isIndexTracker() const { return false; }
+
+    void TrackerBase::open() {
+        m_runState = Executing;
+        moveToThis();
+        if( m_parent )
+            m_parent->openChild();
+    }
+
+    void TrackerBase::close() {
+
+        // Close any still open children (e.g. generators)
+        while( &m_ctx.currentTracker() != this )
+            m_ctx.currentTracker().close();
+
+        switch( m_runState ) {
+            case NeedsAnotherRun:
+                break;
+
+            case Executing:
+                m_runState = CompletedSuccessfully;
+                break;
+            case ExecutingChildren:
+                if( m_children.empty() || m_children.back()->isComplete() )
+                    m_runState = CompletedSuccessfully;
+                break;
+
+            case NotStarted:
+            case CompletedSuccessfully:
+            case Failed:
+                CATCH_INTERNAL_ERROR( "Illogical state: " << m_runState );
+
+            default:
+                CATCH_INTERNAL_ERROR( "Unknown state: " << m_runState );
+        }
+        moveToParent();
+        m_ctx.completeCycle();
+    }
+    void TrackerBase::fail() {
+        m_runState = Failed;
+        if( m_parent )
+            m_parent->markAsNeedingAnotherRun();
+        moveToParent();
+        m_ctx.completeCycle();
+    }
+    void TrackerBase::markAsNeedingAnotherRun() {
+        m_runState = NeedsAnotherRun;
+    }
+
+    void TrackerBase::moveToParent() {
+        assert( m_parent );
+        m_ctx.setCurrentTracker( m_parent );
+    }
+    void TrackerBase::moveToThis() {
+        m_ctx.setCurrentTracker( this );
+    }
+
+    SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )
+    :   TrackerBase( nameAndLocation, ctx, parent )
+    {
+        if( parent ) {
+            while( !parent->isSectionTracker() )
+                parent = &parent->parent();
+
+            SectionTracker& parentSection = static_cast<SectionTracker&>( *parent );
+            addNextFilters( parentSection.m_filters );
+        }
+    }
+
+    bool SectionTracker::isSectionTracker() const { return true; }
+
+    SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) {
+        std::shared_ptr<SectionTracker> section;
+
+        ITracker& currentTracker = ctx.currentTracker();
+        if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) {
+            assert( childTracker );
+            assert( childTracker->isSectionTracker() );
+            section = std::static_pointer_cast<SectionTracker>( childTracker );
+        }
+        else {
+            section = std::make_shared<SectionTracker>( nameAndLocation, ctx, &currentTracker );
+            currentTracker.addChild( section );
+        }
+        if( !ctx.completedCycle() )
+            section->tryOpen();
+        return *section;
+    }
+
+    void SectionTracker::tryOpen() {
+        if( !isComplete() && (m_filters.empty() || m_filters[0].empty() ||  m_filters[0] == m_nameAndLocation.name ) )
+            open();
+    }
+
+    void SectionTracker::addInitialFilters( std::vector<std::string> const& filters ) {
+        if( !filters.empty() ) {
+            m_filters.push_back(""); // Root - should never be consulted
+            m_filters.push_back(""); // Test Case - not a section filter
+            m_filters.insert( m_filters.end(), filters.begin(), filters.end() );
+        }
+    }
+    void SectionTracker::addNextFilters( std::vector<std::string> const& filters ) {
+        if( filters.size() > 1 )
+            m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() );
+    }
+
+    IndexTracker::IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size )
+    :   TrackerBase( nameAndLocation, ctx, parent ),
+        m_size( size )
+    {}
+
+    bool IndexTracker::isIndexTracker() const { return true; }
+
+    IndexTracker& IndexTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) {
+        std::shared_ptr<IndexTracker> tracker;
+
+        ITracker& currentTracker = ctx.currentTracker();
+        if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) {
+            assert( childTracker );
+            assert( childTracker->isIndexTracker() );
+            tracker = std::static_pointer_cast<IndexTracker>( childTracker );
+        }
+        else {
+            tracker = std::make_shared<IndexTracker>( nameAndLocation, ctx, &currentTracker, size );
+            currentTracker.addChild( tracker );
+        }
+
+        if( !ctx.completedCycle() && !tracker->isComplete() ) {
+            if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun )
+                tracker->moveNext();
+            tracker->open();
+        }
+
+        return *tracker;
+    }
+
+    int IndexTracker::index() const { return m_index; }
+
+    void IndexTracker::moveNext() {
+        m_index++;
+        m_children.clear();
+    }
+
+    void IndexTracker::close() {
+        TrackerBase::close();
+        if( m_runState == CompletedSuccessfully && m_index < m_size-1 )
+            m_runState = Executing;
+    }
+
+} // namespace TestCaseTracking
+
+using TestCaseTracking::ITracker;
+using TestCaseTracking::TrackerContext;
+using TestCaseTracking::SectionTracker;
+using TestCaseTracking::IndexTracker;
+
+} // namespace Catch
+
+#if defined(__clang__)
+#    pragma clang diagnostic pop
+#endif
+// end catch_test_case_tracker.cpp
+// start catch_test_registry.cpp
+
+namespace Catch {
+
+    auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker* {
+        return new(std::nothrow) TestInvokerAsFunction( testAsFunction );
+    }
+
+    NameAndTags::NameAndTags( StringRef const& name_ , StringRef const& tags_ ) noexcept : name( name_ ), tags( tags_ ) {}
+
+    AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept {
+        CATCH_TRY {
+            getMutableRegistryHub()
+                    .registerTest(
+                        makeTestCase(
+                            invoker,
+                            extractClassName( classOrMethod ),
+                            nameAndTags,
+                            lineInfo));
+        } CATCH_CATCH_ALL {
+            // Do not throw when constructing global objects, instead register the exception to be processed later
+            getMutableRegistryHub().registerStartupException();
+        }
+    }
+
+    AutoReg::~AutoReg() = default;
+}
+// end catch_test_registry.cpp
+// start catch_test_spec.cpp
+
+#include <algorithm>
+#include <string>
+#include <vector>
+#include <memory>
+
+namespace Catch {
+
+    TestSpec::Pattern::~Pattern() = default;
+    TestSpec::NamePattern::~NamePattern() = default;
+    TestSpec::TagPattern::~TagPattern() = default;
+    TestSpec::ExcludedPattern::~ExcludedPattern() = default;
+
+    TestSpec::NamePattern::NamePattern( std::string const& name )
+    : m_wildcardPattern( toLower( name ), CaseSensitive::No )
+    {}
+    bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const {
+        return m_wildcardPattern.matches( toLower( testCase.name ) );
+    }
+
+    TestSpec::TagPattern::TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {}
+    bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const {
+        return std::find(begin(testCase.lcaseTags),
+                         end(testCase.lcaseTags),
+                         m_tag) != end(testCase.lcaseTags);
+    }
+
+    TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {}
+    bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); }
+
+    bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const {
+        // All patterns in a filter must match for the filter to be a match
+        for( auto const& pattern : m_patterns ) {
+            if( !pattern->matches( testCase ) )
+                return false;
+        }
+        return true;
+    }
+
+    bool TestSpec::hasFilters() const {
+        return !m_filters.empty();
+    }
+    bool TestSpec::matches( TestCaseInfo const& testCase ) const {
+        // A TestSpec matches if any filter matches
+        for( auto const& filter : m_filters )
+            if( filter.matches( testCase ) )
+                return true;
+        return false;
+    }
+}
+// end catch_test_spec.cpp
+// start catch_test_spec_parser.cpp
+
+namespace Catch {
+
+    TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {}
+
+    TestSpecParser& TestSpecParser::parse( std::string const& arg ) {
+        m_mode = None;
+        m_exclusion = false;
+        m_start = std::string::npos;
+        m_arg = m_tagAliases->expandAliases( arg );
+        m_escapeChars.clear();
+        for( m_pos = 0; m_pos < m_arg.size(); ++m_pos )
+            visitChar( m_arg[m_pos] );
+        if( m_mode == Name )
+            addPattern<TestSpec::NamePattern>();
+        return *this;
+    }
+    TestSpec TestSpecParser::testSpec() {
+        addFilter();
+        return m_testSpec;
+    }
+
+    void TestSpecParser::visitChar( char c ) {
+        if( m_mode == None ) {
+            switch( c ) {
+            case ' ': return;
+            case '~': m_exclusion = true; return;
+            case '[': return startNewMode( Tag, ++m_pos );
+            case '"': return startNewMode( QuotedName, ++m_pos );
+            case '\\': return escape();
+            default: startNewMode( Name, m_pos ); break;
+            }
+        }
+        if( m_mode == Name ) {
+            if( c == ',' ) {
+                addPattern<TestSpec::NamePattern>();
+                addFilter();
+            }
+            else if( c == '[' ) {
+                if( subString() == "exclude:" )
+                    m_exclusion = true;
+                else
+                    addPattern<TestSpec::NamePattern>();
+                startNewMode( Tag, ++m_pos );
+            }
+            else if( c == '\\' )
+                escape();
+        }
+        else if( m_mode == EscapedName )
+            m_mode = Name;
+        else if( m_mode == QuotedName && c == '"' )
+            addPattern<TestSpec::NamePattern>();
+        else if( m_mode == Tag && c == ']' )
+            addPattern<TestSpec::TagPattern>();
+    }
+    void TestSpecParser::startNewMode( Mode mode, std::size_t start ) {
+        m_mode = mode;
+        m_start = start;
+    }
+    void TestSpecParser::escape() {
+        if( m_mode == None )
+            m_start = m_pos;
+        m_mode = EscapedName;
+        m_escapeChars.push_back( m_pos );
+    }
+    std::string TestSpecParser::subString() const { return m_arg.substr( m_start, m_pos - m_start ); }
+
+    void TestSpecParser::addFilter() {
+        if( !m_currentFilter.m_patterns.empty() ) {
+            m_testSpec.m_filters.push_back( m_currentFilter );
+            m_currentFilter = TestSpec::Filter();
+        }
+    }
+
+    TestSpec parseTestSpec( std::string const& arg ) {
+        return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec();
+    }
+
+} // namespace Catch
+// end catch_test_spec_parser.cpp
+// start catch_timer.cpp
+
+#include <chrono>
+
+static const uint64_t nanosecondsInSecond = 1000000000;
+
+namespace Catch {
+
+    auto getCurrentNanosecondsSinceEpoch() -> uint64_t {
+        return std::chrono::duration_cast<std::chrono::nanoseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count();
+    }
+
+    namespace {
+        auto estimateClockResolution() -> uint64_t {
+            uint64_t sum = 0;
+            static const uint64_t iterations = 1000000;
+
+            auto startTime = getCurrentNanosecondsSinceEpoch();
+
+            for( std::size_t i = 0; i < iterations; ++i ) {
+
+                uint64_t ticks;
+                uint64_t baseTicks = getCurrentNanosecondsSinceEpoch();
+                do {
+                    ticks = getCurrentNanosecondsSinceEpoch();
+                } while( ticks == baseTicks );
+
+                auto delta = ticks - baseTicks;
+                sum += delta;
+
+                // If we have been calibrating for over 3 seconds -- the clock
+                // is terrible and we should move on.
+                // TBD: How to signal that the measured resolution is probably wrong?
+                if (ticks > startTime + 3 * nanosecondsInSecond) {
+                    return sum / i;
+                }
+            }
+
+            // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers
+            // - and potentially do more iterations if there's a high variance.
+            return sum/iterations;
+        }
+    }
+    auto getEstimatedClockResolution() -> uint64_t {
+        static auto s_resolution = estimateClockResolution();
+        return s_resolution;
+    }
+
+    void Timer::start() {
+       m_nanoseconds = getCurrentNanosecondsSinceEpoch();
+    }
+    auto Timer::getElapsedNanoseconds() const -> uint64_t {
+        return getCurrentNanosecondsSinceEpoch() - m_nanoseconds;
+    }
+    auto Timer::getElapsedMicroseconds() const -> uint64_t {
+        return getElapsedNanoseconds()/1000;
+    }
+    auto Timer::getElapsedMilliseconds() const -> unsigned int {
+        return static_cast<unsigned int>(getElapsedMicroseconds()/1000);
+    }
+    auto Timer::getElapsedSeconds() const -> double {
+        return getElapsedMicroseconds()/1000000.0;
+    }
+
+} // namespace Catch
+// end catch_timer.cpp
+// start catch_tostring.cpp
+
+#if defined(__clang__)
+#    pragma clang diagnostic push
+#    pragma clang diagnostic ignored "-Wexit-time-destructors"
+#    pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+
+// Enable specific decls locally
+#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER)
+#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
+#endif
+
+#include <cmath>
+#include <iomanip>
+
+namespace Catch {
+
+namespace Detail {
+
+    const std::string unprintableString = "{?}";
+
+    namespace {
+        const int hexThreshold = 255;
+
+        struct Endianness {
+            enum Arch { Big, Little };
+
+            static Arch which() {
+                union _{
+                    int asInt;
+                    char asChar[sizeof (int)];
+                } u;
+
+                u.asInt = 1;
+                return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little;
+            }
+        };
+    }
+
+    std::string rawMemoryToString( const void *object, std::size_t size ) {
+        // Reverse order for little endian architectures
+        int i = 0, end = static_cast<int>( size ), inc = 1;
+        if( Endianness::which() == Endianness::Little ) {
+            i = end-1;
+            end = inc = -1;
+        }
+
+        unsigned char const *bytes = static_cast<unsigned char const *>(object);
+        ReusableStringStream rss;
+        rss << "0x" << std::setfill('0') << std::hex;
+        for( ; i != end; i += inc )
+             rss << std::setw(2) << static_cast<unsigned>(bytes[i]);
+       return rss.str();
+    }
+}
+
+template<typename T>
+std::string fpToString( T value, int precision ) {
+    if (Catch::isnan(value)) {
+        return "nan";
+    }
+
+    ReusableStringStream rss;
+    rss << std::setprecision( precision )
+        << std::fixed
+        << value;
+    std::string d = rss.str();
+    std::size_t i = d.find_last_not_of( '0' );
+    if( i != std::string::npos && i != d.size()-1 ) {
+        if( d[i] == '.' )
+            i++;
+        d = d.substr( 0, i+1 );
+    }
+    return d;
+}
+
+//// ======================================================= ////
+//
+//   Out-of-line defs for full specialization of StringMaker
+//
+//// ======================================================= ////
+
+std::string StringMaker<std::string>::convert(const std::string& str) {
+    if (!getCurrentContext().getConfig()->showInvisibles()) {
+        return '"' + str + '"';
+    }
+
+    std::string s("\"");
+    for (char c : str) {
+        switch (c) {
+        case '\n':
+            s.append("\\n");
+            break;
+        case '\t':
+            s.append("\\t");
+            break;
+        default:
+            s.push_back(c);
+            break;
+        }
+    }
+    s.append("\"");
+    return s;
+}
+
+#ifdef CATCH_CONFIG_CPP17_STRING_VIEW
+std::string StringMaker<std::string_view>::convert(std::string_view str) {
+    return ::Catch::Detail::stringify(std::string{ str });
+}
+#endif
+
+std::string StringMaker<char const*>::convert(char const* str) {
+    if (str) {
+        return ::Catch::Detail::stringify(std::string{ str });
+    } else {
+        return{ "{null string}" };
+    }
+}
+std::string StringMaker<char*>::convert(char* str) {
+    if (str) {
+        return ::Catch::Detail::stringify(std::string{ str });
+    } else {
+        return{ "{null string}" };
+    }
+}
+
+#ifdef CATCH_CONFIG_WCHAR
+std::string StringMaker<std::wstring>::convert(const std::wstring& wstr) {
+    std::string s;
+    s.reserve(wstr.size());
+    for (auto c : wstr) {
+        s += (c <= 0xff) ? static_cast<char>(c) : '?';
+    }
+    return ::Catch::Detail::stringify(s);
+}
+
+# ifdef CATCH_CONFIG_CPP17_STRING_VIEW
+std::string StringMaker<std::wstring_view>::convert(std::wstring_view str) {
+    return StringMaker<std::wstring>::convert(std::wstring(str));
+}
+# endif
+
+std::string StringMaker<wchar_t const*>::convert(wchar_t const * str) {
+    if (str) {
+        return ::Catch::Detail::stringify(std::wstring{ str });
+    } else {
+        return{ "{null string}" };
+    }
+}
+std::string StringMaker<wchar_t *>::convert(wchar_t * str) {
+    if (str) {
+        return ::Catch::Detail::stringify(std::wstring{ str });
+    } else {
+        return{ "{null string}" };
+    }
+}
+#endif
+
+std::string StringMaker<int>::convert(int value) {
+    return ::Catch::Detail::stringify(static_cast<long long>(value));
+}
+std::string StringMaker<long>::convert(long value) {
+    return ::Catch::Detail::stringify(static_cast<long long>(value));
+}
+std::string StringMaker<long long>::convert(long long value) {
+    ReusableStringStream rss;
+    rss << value;
+    if (value > Detail::hexThreshold) {
+        rss << " (0x" << std::hex << value << ')';
+    }
+    return rss.str();
+}
+
+std::string StringMaker<unsigned int>::convert(unsigned int value) {
+    return ::Catch::Detail::stringify(static_cast<unsigned long long>(value));
+}
+std::string StringMaker<unsigned long>::convert(unsigned long value) {
+    return ::Catch::Detail::stringify(static_cast<unsigned long long>(value));
+}
+std::string StringMaker<unsigned long long>::convert(unsigned long long value) {
+    ReusableStringStream rss;
+    rss << value;
+    if (value > Detail::hexThreshold) {
+        rss << " (0x" << std::hex << value << ')';
+    }
+    return rss.str();
+}
+
+std::string StringMaker<bool>::convert(bool b) {
+    return b ? "true" : "false";
+}
+
+std::string StringMaker<signed char>::convert(signed char value) {
+    if (value == '\r') {
+        return "'\\r'";
+    } else if (value == '\f') {
+        return "'\\f'";
+    } else if (value == '\n') {
+        return "'\\n'";
+    } else if (value == '\t') {
+        return "'\\t'";
+    } else if ('\0' <= value && value < ' ') {
+        return ::Catch::Detail::stringify(static_cast<unsigned int>(value));
+    } else {
+        char chstr[] = "' '";
+        chstr[1] = value;
+        return chstr;
+    }
+}
+std::string StringMaker<char>::convert(char c) {
+    return ::Catch::Detail::stringify(static_cast<signed char>(c));
+}
+std::string StringMaker<unsigned char>::convert(unsigned char c) {
+    return ::Catch::Detail::stringify(static_cast<char>(c));
+}
+
+std::string StringMaker<std::nullptr_t>::convert(std::nullptr_t) {
+    return "nullptr";
+}
+
+std::string StringMaker<float>::convert(float value) {
+    return fpToString(value, 5) + 'f';
+}
+std::string StringMaker<double>::convert(double value) {
+    return fpToString(value, 10);
+}
+
+std::string ratio_string<std::atto>::symbol() { return "a"; }
+std::string ratio_string<std::femto>::symbol() { return "f"; }
+std::string ratio_string<std::pico>::symbol() { return "p"; }
+std::string ratio_string<std::nano>::symbol() { return "n"; }
+std::string ratio_string<std::micro>::symbol() { return "u"; }
+std::string ratio_string<std::milli>::symbol() { return "m"; }
+
+} // end namespace Catch
+
+#if defined(__clang__)
+#    pragma clang diagnostic pop
+#endif
+
+// end catch_tostring.cpp
+// start catch_totals.cpp
+
+namespace Catch {
+
+    Counts Counts::operator - ( Counts const& other ) const {
+        Counts diff;
+        diff.passed = passed - other.passed;
+        diff.failed = failed - other.failed;
+        diff.failedButOk = failedButOk - other.failedButOk;
+        return diff;
+    }
+
+    Counts& Counts::operator += ( Counts const& other ) {
+        passed += other.passed;
+        failed += other.failed;
+        failedButOk += other.failedButOk;
+        return *this;
+    }
+
+    std::size_t Counts::total() const {
+        return passed + failed + failedButOk;
+    }
+    bool Counts::allPassed() const {
+        return failed == 0 && failedButOk == 0;
+    }
+    bool Counts::allOk() const {
+        return failed == 0;
+    }
+
+    Totals Totals::operator - ( Totals const& other ) const {
+        Totals diff;
+        diff.assertions = assertions - other.assertions;
+        diff.testCases = testCases - other.testCases;
+        return diff;
+    }
+
+    Totals& Totals::operator += ( Totals const& other ) {
+        assertions += other.assertions;
+        testCases += other.testCases;
+        return *this;
+    }
+
+    Totals Totals::delta( Totals const& prevTotals ) const {
+        Totals diff = *this - prevTotals;
+        if( diff.assertions.failed > 0 )
+            ++diff.testCases.failed;
+        else if( diff.assertions.failedButOk > 0 )
+            ++diff.testCases.failedButOk;
+        else
+            ++diff.testCases.passed;
+        return diff;
+    }
+
+}
+// end catch_totals.cpp
+// start catch_uncaught_exceptions.cpp
+
+#include <exception>
+
+namespace Catch {
+    bool uncaught_exceptions() {
+#if defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)
+        return std::uncaught_exceptions() > 0;
+#else
+        return std::uncaught_exception();
+#endif
+  }
+} // end namespace Catch
+// end catch_uncaught_exceptions.cpp
+// start catch_version.cpp
+
+#include <ostream>
+
+namespace Catch {
+
+    Version::Version
+        (   unsigned int _majorVersion,
+            unsigned int _minorVersion,
+            unsigned int _patchNumber,
+            char const * const _branchName,
+            unsigned int _buildNumber )
+    :   majorVersion( _majorVersion ),
+        minorVersion( _minorVersion ),
+        patchNumber( _patchNumber ),
+        branchName( _branchName ),
+        buildNumber( _buildNumber )
+    {}
+
+    std::ostream& operator << ( std::ostream& os, Version const& version ) {
+        os  << version.majorVersion << '.'
+            << version.minorVersion << '.'
+            << version.patchNumber;
+        // branchName is never null -> 0th char is \0 if it is empty
+        if (version.branchName[0]) {
+            os << '-' << version.branchName
+               << '.' << version.buildNumber;
+        }
+        return os;
+    }
+
+    Version const& libraryVersion() {
+        static Version version( 2, 5, 0, "", 0 );
+        return version;
+    }
+
+}
+// end catch_version.cpp
+// start catch_wildcard_pattern.cpp
+
+#include <sstream>
+
+namespace Catch {
+
+    WildcardPattern::WildcardPattern( std::string const& pattern,
+                                      CaseSensitive::Choice caseSensitivity )
+    :   m_caseSensitivity( caseSensitivity ),
+        m_pattern( adjustCase( pattern ) )
+    {
+        if( startsWith( m_pattern, '*' ) ) {
+            m_pattern = m_pattern.substr( 1 );
+            m_wildcard = WildcardAtStart;
+        }
+        if( endsWith( m_pattern, '*' ) ) {
+            m_pattern = m_pattern.substr( 0, m_pattern.size()-1 );
+            m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd );
+        }
+    }
+
+    bool WildcardPattern::matches( std::string const& str ) const {
+        switch( m_wildcard ) {
+            case NoWildcard:
+                return m_pattern == adjustCase( str );
+            case WildcardAtStart:
+                return endsWith( adjustCase( str ), m_pattern );
+            case WildcardAtEnd:
+                return startsWith( adjustCase( str ), m_pattern );
+            case WildcardAtBothEnds:
+                return contains( adjustCase( str ), m_pattern );
+            default:
+                CATCH_INTERNAL_ERROR( "Unknown enum" );
+        }
+    }
+
+    std::string WildcardPattern::adjustCase( std::string const& str ) const {
+        return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str;
+    }
+}
+// end catch_wildcard_pattern.cpp
+// start catch_xmlwriter.cpp
+
+#include <iomanip>
+
+using uchar = unsigned char;
+
+namespace Catch {
+
+namespace {
+
+    size_t trailingBytes(unsigned char c) {
+        if ((c & 0xE0) == 0xC0) {
+            return 2;
+        }
+        if ((c & 0xF0) == 0xE0) {
+            return 3;
+        }
+        if ((c & 0xF8) == 0xF0) {
+            return 4;
+        }
+        CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered");
+    }
+
+    uint32_t headerValue(unsigned char c) {
+        if ((c & 0xE0) == 0xC0) {
+            return c & 0x1F;
+        }
+        if ((c & 0xF0) == 0xE0) {
+            return c & 0x0F;
+        }
+        if ((c & 0xF8) == 0xF0) {
+            return c & 0x07;
+        }
+        CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered");
+    }
+
+    void hexEscapeChar(std::ostream& os, unsigned char c) {
+        os << "\\x"
+            << std::uppercase << std::hex << std::setfill('0') << std::setw(2)
+            << static_cast<int>(c);
+    }
+
+} // anonymous namespace
+
+    XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat )
+    :   m_str( str ),
+        m_forWhat( forWhat )
+    {}
+
+    void XmlEncode::encodeTo( std::ostream& os ) const {
+        // Apostrophe escaping not necessary if we always use " to write attributes
+        // (see: http://www.w3.org/TR/xml/#syntax)
+
+        for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) {
+            uchar c = m_str[idx];
+            switch (c) {
+            case '<':   os << "&lt;"; break;
+            case '&':   os << "&amp;"; break;
+
+            case '>':
+                // See: http://www.w3.org/TR/xml/#syntax
+                if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']')
+                    os << "&gt;";
+                else
+                    os << c;
+                break;
+
+            case '\"':
+                if (m_forWhat == ForAttributes)
+                    os << "&quot;";
+                else
+                    os << c;
+                break;
+
+            default:
+                // Check for control characters and invalid utf-8
+
+                // Escape control characters in standard ascii
+                // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
+                if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) {
+                    hexEscapeChar(os, c);
+                    break;
+                }
+
+                // Plain ASCII: Write it to stream
+                if (c < 0x7F) {
+                    os << c;
+                    break;
+                }
+
+                // UTF-8 territory
+                // Check if the encoding is valid and if it is not, hex escape bytes.
+                // Important: We do not check the exact decoded values for validity, only the encoding format
+                // First check that this bytes is a valid lead byte:
+                // This means that it is not encoded as 1111 1XXX
+                // Or as 10XX XXXX
+                if (c <  0xC0 ||
+                    c >= 0xF8) {
+                    hexEscapeChar(os, c);
+                    break;
+                }
+
+                auto encBytes = trailingBytes(c);
+                // Are there enough bytes left to avoid accessing out-of-bounds memory?
+                if (idx + encBytes - 1 >= m_str.size()) {
+                    hexEscapeChar(os, c);
+                    break;
+                }
+                // The header is valid, check data
+                // The next encBytes bytes must together be a valid utf-8
+                // This means: bitpattern 10XX XXXX and the extracted value is sane (ish)
+                bool valid = true;
+                uint32_t value = headerValue(c);
+                for (std::size_t n = 1; n < encBytes; ++n) {
+                    uchar nc = m_str[idx + n];
+                    valid &= ((nc & 0xC0) == 0x80);
+                    value = (value << 6) | (nc & 0x3F);
+                }
+
+                if (
+                    // Wrong bit pattern of following bytes
+                    (!valid) ||
+                    // Overlong encodings
+                    (value < 0x80) ||
+                    (0x80 <= value && value < 0x800   && encBytes > 2) ||
+                    (0x800 < value && value < 0x10000 && encBytes > 3) ||
+                    // Encoded value out of range
+                    (value >= 0x110000)
+                    ) {
+                    hexEscapeChar(os, c);
+                    break;
+                }
+
+                // If we got here, this is in fact a valid(ish) utf-8 sequence
+                for (std::size_t n = 0; n < encBytes; ++n) {
+                    os << m_str[idx + n];
+                }
+                idx += encBytes - 1;
+                break;
+            }
+        }
+    }
+
+    std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) {
+        xmlEncode.encodeTo( os );
+        return os;
+    }
+
+    XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer )
+    :   m_writer( writer )
+    {}
+
+    XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept
+    :   m_writer( other.m_writer ){
+        other.m_writer = nullptr;
+    }
+    XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept {
+        if ( m_writer ) {
+            m_writer->endElement();
+        }
+        m_writer = other.m_writer;
+        other.m_writer = nullptr;
+        return *this;
+    }
+
+    XmlWriter::ScopedElement::~ScopedElement() {
+        if( m_writer )
+            m_writer->endElement();
+    }
+
+    XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) {
+        m_writer->writeText( text, indent );
+        return *this;
+    }
+
+    XmlWriter::XmlWriter( std::ostream& os ) : m_os( os )
+    {
+        writeDeclaration();
+    }
+
+    XmlWriter::~XmlWriter() {
+        while( !m_tags.empty() )
+            endElement();
+    }
+
+    XmlWriter& XmlWriter::startElement( std::string const& name ) {
+        ensureTagClosed();
+        newlineIfNecessary();
+        m_os << m_indent << '<' << name;
+        m_tags.push_back( name );
+        m_indent += "  ";
+        m_tagIsOpen = true;
+        return *this;
+    }
+
+    XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) {
+        ScopedElement scoped( this );
+        startElement( name );
+        return scoped;
+    }
+
+    XmlWriter& XmlWriter::endElement() {
+        newlineIfNecessary();
+        m_indent = m_indent.substr( 0, m_indent.size()-2 );
+        if( m_tagIsOpen ) {
+            m_os << "/>";
+            m_tagIsOpen = false;
+        }
+        else {
+            m_os << m_indent << "</" << m_tags.back() << ">";
+        }
+        m_os << std::endl;
+        m_tags.pop_back();
+        return *this;
+    }
+
+    XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) {
+        if( !name.empty() && !attribute.empty() )
+            m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"';
+        return *this;
+    }
+
+    XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) {
+        m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"';
+        return *this;
+    }
+
+    XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) {
+        if( !text.empty() ){
+            bool tagWasOpen = m_tagIsOpen;
+            ensureTagClosed();
+            if( tagWasOpen && indent )
+                m_os << m_indent;
+            m_os << XmlEncode( text );
+            m_needsNewline = true;
+        }
+        return *this;
+    }
+
+    XmlWriter& XmlWriter::writeComment( std::string const& text ) {
+        ensureTagClosed();
+        m_os << m_indent << "<!--" << text << "-->";
+        m_needsNewline = true;
+        return *this;
+    }
+
+    void XmlWriter::writeStylesheetRef( std::string const& url ) {
+        m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n";
+    }
+
+    XmlWriter& XmlWriter::writeBlankLine() {
+        ensureTagClosed();
+        m_os << '\n';
+        return *this;
+    }
+
+    void XmlWriter::ensureTagClosed() {
+        if( m_tagIsOpen ) {
+            m_os << ">" << std::endl;
+            m_tagIsOpen = false;
+        }
+    }
+
+    void XmlWriter::writeDeclaration() {
+        m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+    }
+
+    void XmlWriter::newlineIfNecessary() {
+        if( m_needsNewline ) {
+            m_os << std::endl;
+            m_needsNewline = false;
+        }
+    }
+}
+// end catch_xmlwriter.cpp
+// start catch_reporter_bases.cpp
+
+#include <cstring>
+#include <cfloat>
+#include <cstdio>
+#include <cassert>
+#include <memory>
+
+namespace Catch {
+    void prepareExpandedExpression(AssertionResult& result) {
+        result.getExpandedExpression();
+    }
+
+    // Because formatting using c++ streams is stateful, drop down to C is required
+    // Alternatively we could use stringstream, but its performance is... not good.
+    std::string getFormattedDuration( double duration ) {
+        // Max exponent + 1 is required to represent the whole part
+        // + 1 for decimal point
+        // + 3 for the 3 decimal places
+        // + 1 for null terminator
+        const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1;
+        char buffer[maxDoubleSize];
+
+        // Save previous errno, to prevent sprintf from overwriting it
+        ErrnoGuard guard;
+#ifdef _MSC_VER
+        sprintf_s(buffer, "%.3f", duration);
+#else
+        sprintf(buffer, "%.3f", duration);
+#endif
+        return std::string(buffer);
+    }
+
+    TestEventListenerBase::TestEventListenerBase(ReporterConfig const & _config)
+        :StreamingReporterBase(_config) {}
+
+    std::set<Verbosity> TestEventListenerBase::getSupportedVerbosities() {
+        return { Verbosity::Quiet, Verbosity::Normal, Verbosity::High };
+    }
+
+    void TestEventListenerBase::assertionStarting(AssertionInfo const &) {}
+
+    bool TestEventListenerBase::assertionEnded(AssertionStats const &) {
+        return false;
+    }
+
+} // end namespace Catch
+// end catch_reporter_bases.cpp
+// start catch_reporter_compact.cpp
+
+namespace {
+
+#ifdef CATCH_PLATFORM_MAC
+    const char* failedString() { return "FAILED"; }
+    const char* passedString() { return "PASSED"; }
+#else
+    const char* failedString() { return "failed"; }
+    const char* passedString() { return "passed"; }
+#endif
+
+    // Colour::LightGrey
+    Catch::Colour::Code dimColour() { return Catch::Colour::FileName; }
+
+    std::string bothOrAll( std::size_t count ) {
+        return count == 1 ? std::string() :
+               count == 2 ? "both " : "all " ;
+    }
+
+} // anon namespace
+
+namespace Catch {
+namespace {
+// Colour, message variants:
+// - white: No tests ran.
+// -   red: Failed [both/all] N test cases, failed [both/all] M assertions.
+// - white: Passed [both/all] N test cases (no assertions).
+// -   red: Failed N tests cases, failed M assertions.
+// - green: Passed [both/all] N tests cases with M assertions.
+void printTotals(std::ostream& out, const Totals& totals) {
+    if (totals.testCases.total() == 0) {
+        out << "No tests ran.";
+    } else if (totals.testCases.failed == totals.testCases.total()) {
+        Colour colour(Colour::ResultError);
+        const std::string qualify_assertions_failed =
+            totals.assertions.failed == totals.assertions.total() ?
+            bothOrAll(totals.assertions.failed) : std::string();
+        out <<
+            "Failed " << bothOrAll(totals.testCases.failed)
+            << pluralise(totals.testCases.failed, "test case") << ", "
+            "failed " << qualify_assertions_failed <<
+            pluralise(totals.assertions.failed, "assertion") << '.';
+    } else if (totals.assertions.total() == 0) {
+        out <<
+            "Passed " << bothOrAll(totals.testCases.total())
+            << pluralise(totals.testCases.total(), "test case")
+            << " (no assertions).";
+    } else if (totals.assertions.failed) {
+        Colour colour(Colour::ResultError);
+        out <<
+            "Failed " << pluralise(totals.testCases.failed, "test case") << ", "
+            "failed " << pluralise(totals.assertions.failed, "assertion") << '.';
+    } else {
+        Colour colour(Colour::ResultSuccess);
+        out <<
+            "Passed " << bothOrAll(totals.testCases.passed)
+            << pluralise(totals.testCases.passed, "test case") <<
+            " with " << pluralise(totals.assertions.passed, "assertion") << '.';
+    }
+}
+
+// Implementation of CompactReporter formatting
+class AssertionPrinter {
+public:
+    AssertionPrinter& operator= (AssertionPrinter const&) = delete;
+    AssertionPrinter(AssertionPrinter const&) = delete;
+    AssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages)
+        : stream(_stream)
+        , result(_stats.assertionResult)
+        , messages(_stats.infoMessages)
+        , itMessage(_stats.infoMessages.begin())
+        , printInfoMessages(_printInfoMessages) {}
+
+    void print() {
+        printSourceInfo();
+
+        itMessage = messages.begin();
+
+        switch (result.getResultType()) {
+        case ResultWas::Ok:
+            printResultType(Colour::ResultSuccess, passedString());
+            printOriginalExpression();
+            printReconstructedExpression();
+            if (!result.hasExpression())
+                printRemainingMessages(Colour::None);
+            else
+                printRemainingMessages();
+            break;
+        case ResultWas::ExpressionFailed:
+            if (result.isOk())
+                printResultType(Colour::ResultSuccess, failedString() + std::string(" - but was ok"));
+            else
+                printResultType(Colour::Error, failedString());
+            printOriginalExpression();
+            printReconstructedExpression();
+            printRemainingMessages();
+            break;
+        case ResultWas::ThrewException:
+            printResultType(Colour::Error, failedString());
+            printIssue("unexpected exception with message:");
+            printMessage();
+            printExpressionWas();
+            printRemainingMessages();
+            break;
+        case ResultWas::FatalErrorCondition:
+            printResultType(Colour::Error, failedString());
+            printIssue("fatal error condition with message:");
+            printMessage();
+            printExpressionWas();
+            printRemainingMessages();
+            break;
+        case ResultWas::DidntThrowException:
+            printResultType(Colour::Error, failedString());
+            printIssue("expected exception, got none");
+            printExpressionWas();
+            printRemainingMessages();
+            break;
+        case ResultWas::Info:
+            printResultType(Colour::None, "info");
+            printMessage();
+            printRemainingMessages();
+            break;
+        case ResultWas::Warning:
+            printResultType(Colour::None, "warning");
+            printMessage();
+            printRemainingMessages();
+            break;
+        case ResultWas::ExplicitFailure:
+            printResultType(Colour::Error, failedString());
+            printIssue("explicitly");
+            printRemainingMessages(Colour::None);
+            break;
+            // These cases are here to prevent compiler warnings
+        case ResultWas::Unknown:
+        case ResultWas::FailureBit:
+        case ResultWas::Exception:
+            printResultType(Colour::Error, "** internal error **");
+            break;
+        }
+    }
+
+private:
+    void printSourceInfo() const {
+        Colour colourGuard(Colour::FileName);
+        stream << result.getSourceInfo() << ':';
+    }
+
+    void printResultType(Colour::Code colour, std::string const& passOrFail) const {
+        if (!passOrFail.empty()) {
+            {
+                Colour colourGuard(colour);
+                stream << ' ' << passOrFail;
+            }
+            stream << ':';
+        }
+    }
+
+    void printIssue(std::string const& issue) const {
+        stream << ' ' << issue;
+    }
+
+    void printExpressionWas() {
+        if (result.hasExpression()) {
+            stream << ';';
+            {
+                Colour colour(dimColour());
+                stream << " expression was:";
+            }
+            printOriginalExpression();
+        }
+    }
+
+    void printOriginalExpression() const {
+        if (result.hasExpression()) {
+            stream << ' ' << result.getExpression();
+        }
+    }
+
+    void printReconstructedExpression() const {
+        if (result.hasExpandedExpression()) {
+            {
+                Colour colour(dimColour());
+                stream << " for: ";
+            }
+            stream << result.getExpandedExpression();
+        }
+    }
+
+    void printMessage() {
+        if (itMessage != messages.end()) {
+            stream << " '" << itMessage->message << '\'';
+            ++itMessage;
+        }
+    }
+
+    void printRemainingMessages(Colour::Code colour = dimColour()) {
+        if (itMessage == messages.end())
+            return;
+
+        // using messages.end() directly yields (or auto) compilation error:
+        std::vector<MessageInfo>::const_iterator itEnd = messages.end();
+        const std::size_t N = static_cast<std::size_t>(std::distance(itMessage, itEnd));
+
+        {
+            Colour colourGuard(colour);
+            stream << " with " << pluralise(N, "message") << ':';
+        }
+
+        for (; itMessage != itEnd; ) {
+            // If this assertion is a warning ignore any INFO messages
+            if (printInfoMessages || itMessage->type != ResultWas::Info) {
+                stream << " '" << itMessage->message << '\'';
+                if (++itMessage != itEnd) {
+                    Colour colourGuard(dimColour());
+                    stream << " and";
+                }
+            }
+        }
+    }
+
+private:
+    std::ostream& stream;
+    AssertionResult const& result;
+    std::vector<MessageInfo> messages;
+    std::vector<MessageInfo>::const_iterator itMessage;
+    bool printInfoMessages;
+};
+
+} // anon namespace
+
+        std::string CompactReporter::getDescription() {
+            return "Reports test results on a single line, suitable for IDEs";
+        }
+
+        ReporterPreferences CompactReporter::getPreferences() const {
+            return m_reporterPrefs;
+        }
+
+        void CompactReporter::noMatchingTestCases( std::string const& spec ) {
+            stream << "No test cases matched '" << spec << '\'' << std::endl;
+        }
+
+        void CompactReporter::assertionStarting( AssertionInfo const& ) {}
+
+        bool CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) {
+            AssertionResult const& result = _assertionStats.assertionResult;
+
+            bool printInfoMessages = true;
+
+            // Drop out if result was successful and we're not printing those
+            if( !m_config->includeSuccessfulResults() && result.isOk() ) {
+                if( result.getResultType() != ResultWas::Warning )
+                    return false;
+                printInfoMessages = false;
+            }
+
+            AssertionPrinter printer( stream, _assertionStats, printInfoMessages );
+            printer.print();
+
+            stream << std::endl;
+            return true;
+        }
+
+        void CompactReporter::sectionEnded(SectionStats const& _sectionStats) {
+            if (m_config->showDurations() == ShowDurations::Always) {
+                stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl;
+            }
+        }
+
+        void CompactReporter::testRunEnded( TestRunStats const& _testRunStats ) {
+            printTotals( stream, _testRunStats.totals );
+            stream << '\n' << std::endl;
+            StreamingReporterBase::testRunEnded( _testRunStats );
+        }
+
+        CompactReporter::~CompactReporter() {}
+
+    CATCH_REGISTER_REPORTER( "compact", CompactReporter )
+
+} // end namespace Catch
+// end catch_reporter_compact.cpp
+// start catch_reporter_console.cpp
+
+#include <cfloat>
+#include <cstdio>
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch
+ // Note that 4062 (not all labels are handled
+ // and default is missing) is enabled
+#endif
+
+namespace Catch {
+
+namespace {
+
+// Formatter impl for ConsoleReporter
+class ConsoleAssertionPrinter {
+public:
+    ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete;
+    ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete;
+    ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages)
+        : stream(_stream),
+        stats(_stats),
+        result(_stats.assertionResult),
+        colour(Colour::None),
+        message(result.getMessage()),
+        messages(_stats.infoMessages),
+        printInfoMessages(_printInfoMessages) {
+        switch (result.getResultType()) {
+        case ResultWas::Ok:
+            colour = Colour::Success;
+            passOrFail = "PASSED";
+            //if( result.hasMessage() )
+            if (_stats.infoMessages.size() == 1)
+                messageLabel = "with message";
+            if (_stats.infoMessages.size() > 1)
+                messageLabel = "with messages";
+            break;
+        case ResultWas::ExpressionFailed:
+            if (result.isOk()) {
+                colour = Colour::Success;
+                passOrFail = "FAILED - but was ok";
+            } else {
+                colour = Colour::Error;
+                passOrFail = "FAILED";
+            }
+            if (_stats.infoMessages.size() == 1)
+                messageLabel = "with message";
+            if (_stats.infoMessages.size() > 1)
+                messageLabel = "with messages";
+            break;
+        case ResultWas::ThrewException:
+            colour = Colour::Error;
+            passOrFail = "FAILED";
+            messageLabel = "due to unexpected exception with ";
+            if (_stats.infoMessages.size() == 1)
+                messageLabel += "message";
+            if (_stats.infoMessages.size() > 1)
+                messageLabel += "messages";
+            break;
+        case ResultWas::FatalErrorCondition:
+            colour = Colour::Error;
+            passOrFail = "FAILED";
+            messageLabel = "due to a fatal error condition";
+            break;
+        case ResultWas::DidntThrowException:
+            colour = Colour::Error;
+            passOrFail = "FAILED";
+            messageLabel = "because no exception was thrown where one was expected";
+            break;
+        case ResultWas::Info:
+            messageLabel = "info";
+            break;
+        case ResultWas::Warning:
+            messageLabel = "warning";
+            break;
+        case ResultWas::ExplicitFailure:
+            passOrFail = "FAILED";
+            colour = Colour::Error;
+            if (_stats.infoMessages.size() == 1)
+                messageLabel = "explicitly with message";
+            if (_stats.infoMessages.size() > 1)
+                messageLabel = "explicitly with messages";
+            break;
+            // These cases are here to prevent compiler warnings
+        case ResultWas::Unknown:
+        case ResultWas::FailureBit:
+        case ResultWas::Exception:
+            passOrFail = "** internal error **";
+            colour = Colour::Error;
+            break;
+        }
+    }
+
+    void print() const {
+        printSourceInfo();
+        if (stats.totals.assertions.total() > 0) {
+            printResultType();
+            printOriginalExpression();
+            printReconstructedExpression();
+        } else {
+            stream << '\n';
+        }
+        printMessage();
+    }
+
+private:
+    void printResultType() const {
+        if (!passOrFail.empty()) {
+            Colour colourGuard(colour);
+            stream << passOrFail << ":\n";
+        }
+    }
+    void printOriginalExpression() const {
+        if (result.hasExpression()) {
+            Colour colourGuard(Colour::OriginalExpression);
+            stream << "  ";
+            stream << result.getExpressionInMacro();
+            stream << '\n';
+        }
+    }
+    void printReconstructedExpression() const {
+        if (result.hasExpandedExpression()) {
+            stream << "with expansion:\n";
+            Colour colourGuard(Colour::ReconstructedExpression);
+            stream << Column(result.getExpandedExpression()).indent(2) << '\n';
+        }
+    }
+    void printMessage() const {
+        if (!messageLabel.empty())
+            stream << messageLabel << ':' << '\n';
+        for (auto const& msg : messages) {
+            // If this assertion is a warning ignore any INFO messages
+            if (printInfoMessages || msg.type != ResultWas::Info)
+                stream << Column(msg.message).indent(2) << '\n';
+        }
+    }
+    void printSourceInfo() const {
+        Colour colourGuard(Colour::FileName);
+        stream << result.getSourceInfo() << ": ";
+    }
+
+    std::ostream& stream;
+    AssertionStats const& stats;
+    AssertionResult const& result;
+    Colour::Code colour;
+    std::string passOrFail;
+    std::string messageLabel;
+    std::string message;
+    std::vector<MessageInfo> messages;
+    bool printInfoMessages;
+};
+
+std::size_t makeRatio(std::size_t number, std::size_t total) {
+    std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0;
+    return (ratio == 0 && number > 0) ? 1 : ratio;
+}
+
+std::size_t& findMax(std::size_t& i, std::size_t& j, std::size_t& k) {
+    if (i > j && i > k)
+        return i;
+    else if (j > k)
+        return j;
+    else
+        return k;
+}
+
+struct ColumnInfo {
+    enum Justification { Left, Right };
+    std::string name;
+    int width;
+    Justification justification;
+};
+struct ColumnBreak {};
+struct RowBreak {};
+
+class Duration {
+    enum class Unit {
+        Auto,
+        Nanoseconds,
+        Microseconds,
+        Milliseconds,
+        Seconds,
+        Minutes
+    };
+    static const uint64_t s_nanosecondsInAMicrosecond = 1000;
+    static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond;
+    static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond;
+    static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond;
+
+    uint64_t m_inNanoseconds;
+    Unit m_units;
+
+public:
+    explicit Duration(uint64_t inNanoseconds, Unit units = Unit::Auto)
+        : m_inNanoseconds(inNanoseconds),
+        m_units(units) {
+        if (m_units == Unit::Auto) {
+            if (m_inNanoseconds < s_nanosecondsInAMicrosecond)
+                m_units = Unit::Nanoseconds;
+            else if (m_inNanoseconds < s_nanosecondsInAMillisecond)
+                m_units = Unit::Microseconds;
+            else if (m_inNanoseconds < s_nanosecondsInASecond)
+                m_units = Unit::Milliseconds;
+            else if (m_inNanoseconds < s_nanosecondsInAMinute)
+                m_units = Unit::Seconds;
+            else
+                m_units = Unit::Minutes;
+        }
+
+    }
+
+    auto value() const -> double {
+        switch (m_units) {
+        case Unit::Microseconds:
+            return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMicrosecond);
+        case Unit::Milliseconds:
+            return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMillisecond);
+        case Unit::Seconds:
+            return m_inNanoseconds / static_cast<double>(s_nanosecondsInASecond);
+        case Unit::Minutes:
+            return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMinute);
+        default:
+            return static_cast<double>(m_inNanoseconds);
+        }
+    }
+    auto unitsAsString() const -> std::string {
+        switch (m_units) {
+        case Unit::Nanoseconds:
+            return "ns";
+        case Unit::Microseconds:
+            return "µs";
+        case Unit::Milliseconds:
+            return "ms";
+        case Unit::Seconds:
+            return "s";
+        case Unit::Minutes:
+            return "m";
+        default:
+            return "** internal error **";
+        }
+
+    }
+    friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& {
+        return os << duration.value() << " " << duration.unitsAsString();
+    }
+};
+} // end anon namespace
+
+class TablePrinter {
+    std::ostream& m_os;
+    std::vector<ColumnInfo> m_columnInfos;
+    std::ostringstream m_oss;
+    int m_currentColumn = -1;
+    bool m_isOpen = false;
+
+public:
+    TablePrinter( std::ostream& os, std::vector<ColumnInfo> columnInfos )
+    :   m_os( os ),
+        m_columnInfos( std::move( columnInfos ) ) {}
+
+    auto columnInfos() const -> std::vector<ColumnInfo> const& {
+        return m_columnInfos;
+    }
+
+    void open() {
+        if (!m_isOpen) {
+            m_isOpen = true;
+            *this << RowBreak();
+            for (auto const& info : m_columnInfos)
+                *this << info.name << ColumnBreak();
+            *this << RowBreak();
+            m_os << Catch::getLineOfChars<'-'>() << "\n";
+        }
+    }
+    void close() {
+        if (m_isOpen) {
+            *this << RowBreak();
+            m_os << std::endl;
+            m_isOpen = false;
+        }
+    }
+
+    template<typename T>
+    friend TablePrinter& operator << (TablePrinter& tp, T const& value) {
+        tp.m_oss << value;
+        return tp;
+    }
+
+    friend TablePrinter& operator << (TablePrinter& tp, ColumnBreak) {
+        auto colStr = tp.m_oss.str();
+        // This takes account of utf8 encodings
+        auto strSize = Catch::StringRef(colStr).numberOfCharacters();
+        tp.m_oss.str("");
+        tp.open();
+        if (tp.m_currentColumn == static_cast<int>(tp.m_columnInfos.size() - 1)) {
+            tp.m_currentColumn = -1;
+            tp.m_os << "\n";
+        }
+        tp.m_currentColumn++;
+
+        auto colInfo = tp.m_columnInfos[tp.m_currentColumn];
+        auto padding = (strSize + 2 < static_cast<std::size_t>(colInfo.width))
+            ? std::string(colInfo.width - (strSize + 2), ' ')
+            : std::string();
+        if (colInfo.justification == ColumnInfo::Left)
+            tp.m_os << colStr << padding << " ";
+        else
+            tp.m_os << padding << colStr << " ";
+        return tp;
+    }
+
+    friend TablePrinter& operator << (TablePrinter& tp, RowBreak) {
+        if (tp.m_currentColumn > 0) {
+            tp.m_os << "\n";
+            tp.m_currentColumn = -1;
+        }
+        return tp;
+    }
+};
+
+ConsoleReporter::ConsoleReporter(ReporterConfig const& config)
+    : StreamingReporterBase(config),
+    m_tablePrinter(new TablePrinter(config.stream(),
+    {
+        { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 32, ColumnInfo::Left },
+        { "iters", 8, ColumnInfo::Right },
+        { "elapsed ns", 14, ColumnInfo::Right },
+        { "average", 14, ColumnInfo::Right }
+    })) {}
+ConsoleReporter::~ConsoleReporter() = default;
+
+std::string ConsoleReporter::getDescription() {
+    return "Reports test results as plain lines of text";
+}
+
+void ConsoleReporter::noMatchingTestCases(std::string const& spec) {
+    stream << "No test cases matched '" << spec << '\'' << std::endl;
+}
+
+void ConsoleReporter::assertionStarting(AssertionInfo const&) {}
+
+bool ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) {
+    AssertionResult const& result = _assertionStats.assertionResult;
+
+    bool includeResults = m_config->includeSuccessfulResults() || !result.isOk();
+
+    // Drop out if result was successful but we're not printing them.
+    if (!includeResults && result.getResultType() != ResultWas::Warning)
+        return false;
+
+    lazyPrint();
+
+    ConsoleAssertionPrinter printer(stream, _assertionStats, includeResults);
+    printer.print();
+    stream << std::endl;
+    return true;
+}
+
+void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) {
+    m_headerPrinted = false;
+    StreamingReporterBase::sectionStarting(_sectionInfo);
+}
+void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) {
+    m_tablePrinter->close();
+    if (_sectionStats.missingAssertions) {
+        lazyPrint();
+        Colour colour(Colour::ResultError);
+        if (m_sectionStack.size() > 1)
+            stream << "\nNo assertions in section";
+        else
+            stream << "\nNo assertions in test case";
+        stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl;
+    }
+    if (m_config->showDurations() == ShowDurations::Always) {
+        stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl;
+    }
+    if (m_headerPrinted) {
+        m_headerPrinted = false;
+    }
+    StreamingReporterBase::sectionEnded(_sectionStats);
+}
+
+void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) {
+    lazyPrintWithoutClosingBenchmarkTable();
+
+    auto nameCol = Column( info.name ).width( static_cast<std::size_t>( m_tablePrinter->columnInfos()[0].width - 2 ) );
+
+    bool firstLine = true;
+    for (auto line : nameCol) {
+        if (!firstLine)
+            (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak();
+        else
+            firstLine = false;
+
+        (*m_tablePrinter) << line << ColumnBreak();
+    }
+}
+void ConsoleReporter::benchmarkEnded(BenchmarkStats const& stats) {
+    Duration average(stats.elapsedTimeInNanoseconds / stats.iterations);
+    (*m_tablePrinter)
+        << stats.iterations << ColumnBreak()
+        << stats.elapsedTimeInNanoseconds << ColumnBreak()
+        << average << ColumnBreak();
+}
+
+void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) {
+    m_tablePrinter->close();
+    StreamingReporterBase::testCaseEnded(_testCaseStats);
+    m_headerPrinted = false;
+}
+void ConsoleReporter::testGroupEnded(TestGroupStats const& _testGroupStats) {
+    if (currentGroupInfo.used) {
+        printSummaryDivider();
+        stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n";
+        printTotals(_testGroupStats.totals);
+        stream << '\n' << std::endl;
+    }
+    StreamingReporterBase::testGroupEnded(_testGroupStats);
+}
+void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) {
+    printTotalsDivider(_testRunStats.totals);
+    printTotals(_testRunStats.totals);
+    stream << std::endl;
+    StreamingReporterBase::testRunEnded(_testRunStats);
+}
+
+void ConsoleReporter::lazyPrint() {
+
+    m_tablePrinter->close();
+    lazyPrintWithoutClosingBenchmarkTable();
+}
+
+void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() {
+
+    if (!currentTestRunInfo.used)
+        lazyPrintRunInfo();
+    if (!currentGroupInfo.used)
+        lazyPrintGroupInfo();
+
+    if (!m_headerPrinted) {
+        printTestCaseAndSectionHeader();
+        m_headerPrinted = true;
+    }
+}
+void ConsoleReporter::lazyPrintRunInfo() {
+    stream << '\n' << getLineOfChars<'~'>() << '\n';
+    Colour colour(Colour::SecondaryText);
+    stream << currentTestRunInfo->name
+        << " is a Catch v" << libraryVersion() << " host application.\n"
+        << "Run with -? for options\n\n";
+
+    if (m_config->rngSeed() != 0)
+        stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n";
+
+    currentTestRunInfo.used = true;
+}
+void ConsoleReporter::lazyPrintGroupInfo() {
+    if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) {
+        printClosedHeader("Group: " + currentGroupInfo->name);
+        currentGroupInfo.used = true;
+    }
+}
+void ConsoleReporter::printTestCaseAndSectionHeader() {
+    assert(!m_sectionStack.empty());
+    printOpenHeader(currentTestCaseInfo->name);
+
+    if (m_sectionStack.size() > 1) {
+        Colour colourGuard(Colour::Headers);
+
+        auto
+            it = m_sectionStack.begin() + 1, // Skip first section (test case)
+            itEnd = m_sectionStack.end();
+        for (; it != itEnd; ++it)
+            printHeaderString(it->name, 2);
+    }
+
+    SourceLineInfo lineInfo = m_sectionStack.back().lineInfo;
+
+    if (!lineInfo.empty()) {
+        stream << getLineOfChars<'-'>() << '\n';
+        Colour colourGuard(Colour::FileName);
+        stream << lineInfo << '\n';
+    }
+    stream << getLineOfChars<'.'>() << '\n' << std::endl;
+}
+
+void ConsoleReporter::printClosedHeader(std::string const& _name) {
+    printOpenHeader(_name);
+    stream << getLineOfChars<'.'>() << '\n';
+}
+void ConsoleReporter::printOpenHeader(std::string const& _name) {
+    stream << getLineOfChars<'-'>() << '\n';
+    {
+        Colour colourGuard(Colour::Headers);
+        printHeaderString(_name);
+    }
+}
+
+// if string has a : in first line will set indent to follow it on
+// subsequent lines
+void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) {
+    std::size_t i = _string.find(": ");
+    if (i != std::string::npos)
+        i += 2;
+    else
+        i = 0;
+    stream << Column(_string).indent(indent + i).initialIndent(indent) << '\n';
+}
+
+struct SummaryColumn {
+
+    SummaryColumn( std::string _label, Colour::Code _colour )
+    :   label( std::move( _label ) ),
+        colour( _colour ) {}
+    SummaryColumn addRow( std::size_t count ) {
+        ReusableStringStream rss;
+        rss << count;
+        std::string row = rss.str();
+        for (auto& oldRow : rows) {
+            while (oldRow.size() < row.size())
+                oldRow = ' ' + oldRow;
+            while (oldRow.size() > row.size())
+                row = ' ' + row;
+        }
+        rows.push_back(row);
+        return *this;
+    }
+
+    std::string label;
+    Colour::Code colour;
+    std::vector<std::string> rows;
+
+};
+
+void ConsoleReporter::printTotals( Totals const& totals ) {
+    if (totals.testCases.total() == 0) {
+        stream << Colour(Colour::Warning) << "No tests ran\n";
+    } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) {
+        stream << Colour(Colour::ResultSuccess) << "All tests passed";
+        stream << " ("
+            << pluralise(totals.assertions.passed, "assertion") << " in "
+            << pluralise(totals.testCases.passed, "test case") << ')'
+            << '\n';
+    } else {
+
+        std::vector<SummaryColumn> columns;
+        columns.push_back(SummaryColumn("", Colour::None)
+                          .addRow(totals.testCases.total())
+                          .addRow(totals.assertions.total()));
+        columns.push_back(SummaryColumn("passed", Colour::Success)
+                          .addRow(totals.testCases.passed)
+                          .addRow(totals.assertions.passed));
+        columns.push_back(SummaryColumn("failed", Colour::ResultError)
+                          .addRow(totals.testCases.failed)
+                          .addRow(totals.assertions.failed));
+        columns.push_back(SummaryColumn("failed as expected", Colour::ResultExpectedFailure)
+                          .addRow(totals.testCases.failedButOk)
+                          .addRow(totals.assertions.failedButOk));
+
+        printSummaryRow("test cases", columns, 0);
+        printSummaryRow("assertions", columns, 1);
+    }
+}
+void ConsoleReporter::printSummaryRow(std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row) {
+    for (auto col : cols) {
+        std::string value = col.rows[row];
+        if (col.label.empty()) {
+            stream << label << ": ";
+            if (value != "0")
+                stream << value;
+            else
+                stream << Colour(Colour::Warning) << "- none -";
+        } else if (value != "0") {
+            stream << Colour(Colour::LightGrey) << " | ";
+            stream << Colour(col.colour)
+                << value << ' ' << col.label;
+        }
+    }
+    stream << '\n';
+}
+
+void ConsoleReporter::printTotalsDivider(Totals const& totals) {
+    if (totals.testCases.total() > 0) {
+        std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total());
+        std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total());
+        std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total());
+        while (failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1)
+            findMax(failedRatio, failedButOkRatio, passedRatio)++;
+        while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1)
+            findMax(failedRatio, failedButOkRatio, passedRatio)--;
+
+        stream << Colour(Colour::Error) << std::string(failedRatio, '=');
+        stream << Colour(Colour::ResultExpectedFailure) << std::string(failedButOkRatio, '=');
+        if (totals.testCases.allPassed())
+            stream << Colour(Colour::ResultSuccess) << std::string(passedRatio, '=');
+        else
+            stream << Colour(Colour::Success) << std::string(passedRatio, '=');
+    } else {
+        stream << Colour(Colour::Warning) << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '=');
+    }
+    stream << '\n';
+}
+void ConsoleReporter::printSummaryDivider() {
+    stream << getLineOfChars<'-'>() << '\n';
+}
+
+CATCH_REGISTER_REPORTER("console", ConsoleReporter)
+
+} // end namespace Catch
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+// end catch_reporter_console.cpp
+// start catch_reporter_junit.cpp
+
+#include <cassert>
+#include <sstream>
+#include <ctime>
+#include <algorithm>
+
+namespace Catch {
+
+    namespace {
+        std::string getCurrentTimestamp() {
+            // Beware, this is not reentrant because of backward compatibility issues
+            // Also, UTC only, again because of backward compatibility (%z is C++11)
+            time_t rawtime;
+            std::time(&rawtime);
+            auto const timeStampSize = sizeof("2017-01-16T17:06:45Z");
+
+#ifdef _MSC_VER
+            std::tm timeInfo = {};
+            gmtime_s(&timeInfo, &rawtime);
+#else
+            std::tm* timeInfo;
+            timeInfo = std::gmtime(&rawtime);
+#endif
+
+            char timeStamp[timeStampSize];
+            const char * const fmt = "%Y-%m-%dT%H:%M:%SZ";
+
+#ifdef _MSC_VER
+            std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);
+#else
+            std::strftime(timeStamp, timeStampSize, fmt, timeInfo);
+#endif
+            return std::string(timeStamp);
+        }
+
+        std::string fileNameTag(const std::vector<std::string> &tags) {
+            auto it = std::find_if(begin(tags),
+                                   end(tags),
+                                   [] (std::string const& tag) {return tag.front() == '#'; });
+            if (it != tags.end())
+                return it->substr(1);
+            return std::string();
+        }
+    } // anonymous namespace
+
+    JunitReporter::JunitReporter( ReporterConfig const& _config )
+        :   CumulativeReporterBase( _config ),
+            xml( _config.stream() )
+        {
+            m_reporterPrefs.shouldRedirectStdOut = true;
+            m_reporterPrefs.shouldReportAllAssertions = true;
+        }
+
+    JunitReporter::~JunitReporter() {}
+
+    std::string JunitReporter::getDescription() {
+        return "Reports test results in an XML format that looks like Ant's junitreport target";
+    }
+
+    void JunitReporter::noMatchingTestCases( std::string const& /*spec*/ ) {}
+
+    void JunitReporter::testRunStarting( TestRunInfo const& runInfo )  {
+        CumulativeReporterBase::testRunStarting( runInfo );
+        xml.startElement( "testsuites" );
+    }
+
+    void JunitReporter::testGroupStarting( GroupInfo const& groupInfo ) {
+        suiteTimer.start();
+        stdOutForSuite.clear();
+        stdErrForSuite.clear();
+        unexpectedExceptions = 0;
+        CumulativeReporterBase::testGroupStarting( groupInfo );
+    }
+
+    void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) {
+        m_okToFail = testCaseInfo.okToFail();
+    }
+
+    bool JunitReporter::assertionEnded( AssertionStats const& assertionStats ) {
+        if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail )
+            unexpectedExceptions++;
+        return CumulativeReporterBase::assertionEnded( assertionStats );
+    }
+
+    void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) {
+        stdOutForSuite += testCaseStats.stdOut;
+        stdErrForSuite += testCaseStats.stdErr;
+        CumulativeReporterBase::testCaseEnded( testCaseStats );
+    }
+
+    void JunitReporter::testGroupEnded( TestGroupStats const& testGroupStats ) {
+        double suiteTime = suiteTimer.getElapsedSeconds();
+        CumulativeReporterBase::testGroupEnded( testGroupStats );
+        writeGroup( *m_testGroups.back(), suiteTime );
+    }
+
+    void JunitReporter::testRunEndedCumulative() {
+        xml.endElement();
+    }
+
+    void JunitReporter::writeGroup( TestGroupNode const& groupNode, double suiteTime ) {
+        XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" );
+        TestGroupStats const& stats = groupNode.value;
+        xml.writeAttribute( "name", stats.groupInfo.name );
+        xml.writeAttribute( "errors", unexpectedExceptions );
+        xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions );
+        xml.writeAttribute( "tests", stats.totals.assertions.total() );
+        xml.writeAttribute( "hostname", "tbd" ); // !TBD
+        if( m_config->showDurations() == ShowDurations::Never )
+            xml.writeAttribute( "time", "" );
+        else
+            xml.writeAttribute( "time", suiteTime );
+        xml.writeAttribute( "timestamp", getCurrentTimestamp() );
+
+        // Write test cases
+        for( auto const& child : groupNode.children )
+            writeTestCase( *child );
+
+        xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), false );
+        xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), false );
+    }
+
+    void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) {
+        TestCaseStats const& stats = testCaseNode.value;
+
+        // All test cases have exactly one section - which represents the
+        // test case itself. That section may have 0-n nested sections
+        assert( testCaseNode.children.size() == 1 );
+        SectionNode const& rootSection = *testCaseNode.children.front();
+
+        std::string className = stats.testInfo.className;
+
+        if( className.empty() ) {
+            className = fileNameTag(stats.testInfo.tags);
+            if ( className.empty() )
+                className = "global";
+        }
+
+        if ( !m_config->name().empty() )
+            className = m_config->name() + "." + className;
+
+        writeSection( className, "", rootSection );
+    }
+
+    void JunitReporter::writeSection(  std::string const& className,
+                        std::string const& rootName,
+                        SectionNode const& sectionNode ) {
+        std::string name = trim( sectionNode.stats.sectionInfo.name );
+        if( !rootName.empty() )
+            name = rootName + '/' + name;
+
+        if( !sectionNode.assertions.empty() ||
+            !sectionNode.stdOut.empty() ||
+            !sectionNode.stdErr.empty() ) {
+            XmlWriter::ScopedElement e = xml.scopedElement( "testcase" );
+            if( className.empty() ) {
+                xml.writeAttribute( "classname", name );
+                xml.writeAttribute( "name", "root" );
+            }
+            else {
+                xml.writeAttribute( "classname", className );
+                xml.writeAttribute( "name", name );
+            }
+            xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) );
+
+            writeAssertions( sectionNode );
+
+            if( !sectionNode.stdOut.empty() )
+                xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false );
+            if( !sectionNode.stdErr.empty() )
+                xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false );
+        }
+        for( auto const& childNode : sectionNode.childSections )
+            if( className.empty() )
+                writeSection( name, "", *childNode );
+            else
+                writeSection( className, name, *childNode );
+    }
+
+    void JunitReporter::writeAssertions( SectionNode const& sectionNode ) {
+        for( auto const& assertion : sectionNode.assertions )
+            writeAssertion( assertion );
+    }
+
+    void JunitReporter::writeAssertion( AssertionStats const& stats ) {
+        AssertionResult const& result = stats.assertionResult;
+        if( !result.isOk() ) {
+            std::string elementName;
+            switch( result.getResultType() ) {
+                case ResultWas::ThrewException:
+                case ResultWas::FatalErrorCondition:
+                    elementName = "error";
+                    break;
+                case ResultWas::ExplicitFailure:
+                    elementName = "failure";
+                    break;
+                case ResultWas::ExpressionFailed:
+                    elementName = "failure";
+                    break;
+                case ResultWas::DidntThrowException:
+                    elementName = "failure";
+                    break;
+
+                // We should never see these here:
+                case ResultWas::Info:
+                case ResultWas::Warning:
+                case ResultWas::Ok:
+                case ResultWas::Unknown:
+                case ResultWas::FailureBit:
+                case ResultWas::Exception:
+                    elementName = "internalError";
+                    break;
+            }
+
+            XmlWriter::ScopedElement e = xml.scopedElement( elementName );
+
+            xml.writeAttribute( "message", result.getExpandedExpression() );
+            xml.writeAttribute( "type", result.getTestMacroName() );
+
+            ReusableStringStream rss;
+            if( !result.getMessage().empty() )
+                rss << result.getMessage() << '\n';
+            for( auto const& msg : stats.infoMessages )
+                if( msg.type == ResultWas::Info )
+                    rss << msg.message << '\n';
+
+            rss << "at " << result.getSourceInfo();
+            xml.writeText( rss.str(), false );
+        }
+    }
+
+    CATCH_REGISTER_REPORTER( "junit", JunitReporter )
+
+} // end namespace Catch
+// end catch_reporter_junit.cpp
+// start catch_reporter_listening.cpp
+
+#include <cassert>
+
+namespace Catch {
+
+    ListeningReporter::ListeningReporter() {
+        // We will assume that listeners will always want all assertions
+        m_preferences.shouldReportAllAssertions = true;
+    }
+
+    void ListeningReporter::addListener( IStreamingReporterPtr&& listener ) {
+        m_listeners.push_back( std::move( listener ) );
+    }
+
+    void ListeningReporter::addReporter(IStreamingReporterPtr&& reporter) {
+        assert(!m_reporter && "Listening reporter can wrap only 1 real reporter");
+        m_reporter = std::move( reporter );
+        m_preferences.shouldRedirectStdOut = m_reporter->getPreferences().shouldRedirectStdOut;
+    }
+
+    ReporterPreferences ListeningReporter::getPreferences() const {
+        return m_preferences;
+    }
+
+    std::set<Verbosity> ListeningReporter::getSupportedVerbosities() {
+        return std::set<Verbosity>{ };
+    }
+
+    void ListeningReporter::noMatchingTestCases( std::string const& spec ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->noMatchingTestCases( spec );
+        }
+        m_reporter->noMatchingTestCases( spec );
+    }
+
+    void ListeningReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->benchmarkStarting( benchmarkInfo );
+        }
+        m_reporter->benchmarkStarting( benchmarkInfo );
+    }
+    void ListeningReporter::benchmarkEnded( BenchmarkStats const& benchmarkStats ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->benchmarkEnded( benchmarkStats );
+        }
+        m_reporter->benchmarkEnded( benchmarkStats );
+    }
+
+    void ListeningReporter::testRunStarting( TestRunInfo const& testRunInfo ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->testRunStarting( testRunInfo );
+        }
+        m_reporter->testRunStarting( testRunInfo );
+    }
+
+    void ListeningReporter::testGroupStarting( GroupInfo const& groupInfo ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->testGroupStarting( groupInfo );
+        }
+        m_reporter->testGroupStarting( groupInfo );
+    }
+
+    void ListeningReporter::testCaseStarting( TestCaseInfo const& testInfo ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->testCaseStarting( testInfo );
+        }
+        m_reporter->testCaseStarting( testInfo );
+    }
+
+    void ListeningReporter::sectionStarting( SectionInfo const& sectionInfo ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->sectionStarting( sectionInfo );
+        }
+        m_reporter->sectionStarting( sectionInfo );
+    }
+
+    void ListeningReporter::assertionStarting( AssertionInfo const& assertionInfo ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->assertionStarting( assertionInfo );
+        }
+        m_reporter->assertionStarting( assertionInfo );
+    }
+
+    // The return value indicates if the messages buffer should be cleared:
+    bool ListeningReporter::assertionEnded( AssertionStats const& assertionStats ) {
+        for( auto const& listener : m_listeners ) {
+            static_cast<void>( listener->assertionEnded( assertionStats ) );
+        }
+        return m_reporter->assertionEnded( assertionStats );
+    }
+
+    void ListeningReporter::sectionEnded( SectionStats const& sectionStats ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->sectionEnded( sectionStats );
+        }
+        m_reporter->sectionEnded( sectionStats );
+    }
+
+    void ListeningReporter::testCaseEnded( TestCaseStats const& testCaseStats ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->testCaseEnded( testCaseStats );
+        }
+        m_reporter->testCaseEnded( testCaseStats );
+    }
+
+    void ListeningReporter::testGroupEnded( TestGroupStats const& testGroupStats ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->testGroupEnded( testGroupStats );
+        }
+        m_reporter->testGroupEnded( testGroupStats );
+    }
+
+    void ListeningReporter::testRunEnded( TestRunStats const& testRunStats ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->testRunEnded( testRunStats );
+        }
+        m_reporter->testRunEnded( testRunStats );
+    }
+
+    void ListeningReporter::skipTest( TestCaseInfo const& testInfo ) {
+        for ( auto const& listener : m_listeners ) {
+            listener->skipTest( testInfo );
+        }
+        m_reporter->skipTest( testInfo );
+    }
+
+    bool ListeningReporter::isMulti() const {
+        return true;
+    }
+
+} // end namespace Catch
+// end catch_reporter_listening.cpp
+// start catch_reporter_xml.cpp
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch
+                              // Note that 4062 (not all labels are handled
+                              // and default is missing) is enabled
+#endif
+
+namespace Catch {
+    XmlReporter::XmlReporter( ReporterConfig const& _config )
+    :   StreamingReporterBase( _config ),
+        m_xml(_config.stream())
+    {
+        m_reporterPrefs.shouldRedirectStdOut = true;
+        m_reporterPrefs.shouldReportAllAssertions = true;
+    }
+
+    XmlReporter::~XmlReporter() = default;
+
+    std::string XmlReporter::getDescription() {
+        return "Reports test results as an XML document";
+    }
+
+    std::string XmlReporter::getStylesheetRef() const {
+        return std::string();
+    }
+
+    void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) {
+        m_xml
+            .writeAttribute( "filename", sourceInfo.file )
+            .writeAttribute( "line", sourceInfo.line );
+    }
+
+    void XmlReporter::noMatchingTestCases( std::string const& s ) {
+        StreamingReporterBase::noMatchingTestCases( s );
+    }
+
+    void XmlReporter::testRunStarting( TestRunInfo const& testInfo ) {
+        StreamingReporterBase::testRunStarting( testInfo );
+        std::string stylesheetRef = getStylesheetRef();
+        if( !stylesheetRef.empty() )
+            m_xml.writeStylesheetRef( stylesheetRef );
+        m_xml.startElement( "Catch" );
+        if( !m_config->name().empty() )
+            m_xml.writeAttribute( "name", m_config->name() );
+        if( m_config->rngSeed() != 0 )
+            m_xml.scopedElement( "Randomness" )
+                .writeAttribute( "seed", m_config->rngSeed() );
+    }
+
+    void XmlReporter::testGroupStarting( GroupInfo const& groupInfo ) {
+        StreamingReporterBase::testGroupStarting( groupInfo );
+        m_xml.startElement( "Group" )
+            .writeAttribute( "name", groupInfo.name );
+    }
+
+    void XmlReporter::testCaseStarting( TestCaseInfo const& testInfo ) {
+        StreamingReporterBase::testCaseStarting(testInfo);
+        m_xml.startElement( "TestCase" )
+            .writeAttribute( "name", trim( testInfo.name ) )
+            .writeAttribute( "description", testInfo.description )
+            .writeAttribute( "tags", testInfo.tagsAsString() );
+
+        writeSourceInfo( testInfo.lineInfo );
+
+        if ( m_config->showDurations() == ShowDurations::Always )
+            m_testCaseTimer.start();
+        m_xml.ensureTagClosed();
+    }
+
+    void XmlReporter::sectionStarting( SectionInfo const& sectionInfo ) {
+        StreamingReporterBase::sectionStarting( sectionInfo );
+        if( m_sectionDepth++ > 0 ) {
+            m_xml.startElement( "Section" )
+                .writeAttribute( "name", trim( sectionInfo.name ) );
+            writeSourceInfo( sectionInfo.lineInfo );
+            m_xml.ensureTagClosed();
+        }
+    }
+
+    void XmlReporter::assertionStarting( AssertionInfo const& ) { }
+
+    bool XmlReporter::assertionEnded( AssertionStats const& assertionStats ) {
+
+        AssertionResult const& result = assertionStats.assertionResult;
+
+        bool includeResults = m_config->includeSuccessfulResults() || !result.isOk();
+
+        if( includeResults || result.getResultType() == ResultWas::Warning ) {
+            // Print any info messages in <Info> tags.
+            for( auto const& msg : assertionStats.infoMessages ) {
+                if( msg.type == ResultWas::Info && includeResults ) {
+                    m_xml.scopedElement( "Info" )
+                            .writeText( msg.message );
+                } else if ( msg.type == ResultWas::Warning ) {
+                    m_xml.scopedElement( "Warning" )
+                            .writeText( msg.message );
+                }
+            }
+        }
+
+        // Drop out if result was successful but we're not printing them.
+        if( !includeResults && result.getResultType() != ResultWas::Warning )
+            return true;
+
+        // Print the expression if there is one.
+        if( result.hasExpression() ) {
+            m_xml.startElement( "Expression" )
+                .writeAttribute( "success", result.succeeded() )
+                .writeAttribute( "type", result.getTestMacroName() );
+
+            writeSourceInfo( result.getSourceInfo() );
+
+            m_xml.scopedElement( "Original" )
+                .writeText( result.getExpression() );
+            m_xml.scopedElement( "Expanded" )
+                .writeText( result.getExpandedExpression() );
+        }
+
+        // And... Print a result applicable to each result type.
+        switch( result.getResultType() ) {
+            case ResultWas::ThrewException:
+                m_xml.startElement( "Exception" );
+                writeSourceInfo( result.getSourceInfo() );
+                m_xml.writeText( result.getMessage() );
+                m_xml.endElement();
+                break;
+            case ResultWas::FatalErrorCondition:
+                m_xml.startElement( "FatalErrorCondition" );
+                writeSourceInfo( result.getSourceInfo() );
+                m_xml.writeText( result.getMessage() );
+                m_xml.endElement();
+                break;
+            case ResultWas::Info:
+                m_xml.scopedElement( "Info" )
+                    .writeText( result.getMessage() );
+                break;
+            case ResultWas::Warning:
+                // Warning will already have been written
+                break;
+            case ResultWas::ExplicitFailure:
+                m_xml.startElement( "Failure" );
+                writeSourceInfo( result.getSourceInfo() );
+                m_xml.writeText( result.getMessage() );
+                m_xml.endElement();
+                break;
+            default:
+                break;
+        }
+
+        if( result.hasExpression() )
+            m_xml.endElement();
+
+        return true;
+    }
+
+    void XmlReporter::sectionEnded( SectionStats const& sectionStats ) {
+        StreamingReporterBase::sectionEnded( sectionStats );
+        if( --m_sectionDepth > 0 ) {
+            XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" );
+            e.writeAttribute( "successes", sectionStats.assertions.passed );
+            e.writeAttribute( "failures", sectionStats.assertions.failed );
+            e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk );
+
+            if ( m_config->showDurations() == ShowDurations::Always )
+                e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds );
+
+            m_xml.endElement();
+        }
+    }
+
+    void XmlReporter::testCaseEnded( TestCaseStats const& testCaseStats ) {
+        StreamingReporterBase::testCaseEnded( testCaseStats );
+        XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" );
+        e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() );
+
+        if ( m_config->showDurations() == ShowDurations::Always )
+            e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() );
+
+        if( !testCaseStats.stdOut.empty() )
+            m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false );
+        if( !testCaseStats.stdErr.empty() )
+            m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false );
+
+        m_xml.endElement();
+    }
+
+    void XmlReporter::testGroupEnded( TestGroupStats const& testGroupStats ) {
+        StreamingReporterBase::testGroupEnded( testGroupStats );
+        // TODO: Check testGroupStats.aborting and act accordingly.
+        m_xml.scopedElement( "OverallResults" )
+            .writeAttribute( "successes", testGroupStats.totals.assertions.passed )
+            .writeAttribute( "failures", testGroupStats.totals.assertions.failed )
+            .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk );
+        m_xml.endElement();
+    }
+
+    void XmlReporter::testRunEnded( TestRunStats const& testRunStats ) {
+        StreamingReporterBase::testRunEnded( testRunStats );
+        m_xml.scopedElement( "OverallResults" )
+            .writeAttribute( "successes", testRunStats.totals.assertions.passed )
+            .writeAttribute( "failures", testRunStats.totals.assertions.failed )
+            .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk );
+        m_xml.endElement();
+    }
+
+    CATCH_REGISTER_REPORTER( "xml", XmlReporter )
+
+} // end namespace Catch
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+// end catch_reporter_xml.cpp
+
+namespace Catch {
+    LeakDetector leakDetector;
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+// end catch_impl.hpp
+#endif
+
+#ifdef CATCH_CONFIG_MAIN
+// start catch_default_main.hpp
+
+#ifndef __OBJC__
+
+#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN)
+// Standard C/C++ Win32 Unicode wmain entry point
+extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) {
+#else
+// Standard C/C++ main entry point
+int main (int argc, char * argv[]) {
+#endif
+
+    return Catch::Session().run( argc, argv );
+}
+
+#else // __OBJC__
+
+// Objective-C entry point
+int main (int argc, char * const argv[]) {
+#if !CATCH_ARC_ENABLED
+    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+#endif
+
+    Catch::registerTestMethods();
+    int result = Catch::Session().run( argc, (char**)argv );
+
+#if !CATCH_ARC_ENABLED
+    [pool drain];
+#endif
+
+    return result;
+}
+
+#endif // __OBJC__
+
+// end catch_default_main.hpp
+#endif
+
+#if !defined(CATCH_CONFIG_IMPL_ONLY)
+
+#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED
+#  undef CLARA_CONFIG_MAIN
+#endif
+
+#if !defined(CATCH_CONFIG_DISABLE)
+//////
+// If this config identifier is defined then all CATCH macros are prefixed with CATCH_
+#ifdef CATCH_CONFIG_PREFIX_ALL
+
+#define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ )
+#define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )
+
+#define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", __VA_ARGS__ )
+#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr )
+#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr )
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr )
+#endif// CATCH_CONFIG_DISABLE_MATCHERS
+#define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ )
+
+#define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )
+#define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )
+
+#define CATCH_CHECK_THROWS( ... )  INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", __VA_ARGS__ )
+#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr )
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr )
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+#define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )
+
+#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+
+#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg )
+#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )
+#define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE",__VA_ARGS__ )
+
+#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
+#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
+#define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
+#define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
+#define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
+#define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ )
+#define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )
+#define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+
+#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE()
+
+#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ )
+#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )
+#else
+#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) )
+#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) )
+#endif
+
+#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE)
+#define CATCH_STATIC_REQUIRE( ... )       static_assert(   __VA_ARGS__ ,      #__VA_ARGS__ );     CATCH_SUCCEED( #__VA_ARGS__ )
+#define CATCH_STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ )
+#else
+#define CATCH_STATIC_REQUIRE( ... )       CATCH_REQUIRE( __VA_ARGS__ )
+#define CATCH_STATIC_REQUIRE_FALSE( ... ) CATCH_REQUIRE_FALSE( __VA_ARGS__ )
+#endif
+
+// "BDD-style" convenience wrappers
+#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ )
+#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ )
+#define CATCH_GIVEN( desc )     INTERNAL_CATCH_DYNAMIC_SECTION( "    Given: " << desc )
+#define CATCH_AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc )
+#define CATCH_WHEN( desc )      INTERNAL_CATCH_DYNAMIC_SECTION( "     When: " << desc )
+#define CATCH_AND_WHEN( desc )  INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc )
+#define CATCH_THEN( desc )      INTERNAL_CATCH_DYNAMIC_SECTION( "     Then: " << desc )
+#define CATCH_AND_THEN( desc )  INTERNAL_CATCH_DYNAMIC_SECTION( "      And: " << desc )
+
+// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required
+#else
+
+#define REQUIRE( ... ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__  )
+#define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )
+
+#define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ )
+#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr )
+#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr )
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr )
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+#define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ )
+
+#define CHECK( ... ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )
+#define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )
+
+#define CHECK_THROWS( ... )  INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr )
+#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr )
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr )
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+#define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )
+
+#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+
+#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg )
+#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )
+#define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE",__VA_ARGS__ )
+
+#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
+#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
+#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
+#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
+#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
+#define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ )
+#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )
+#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
+#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE()
+
+#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ )
+#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )
+#else
+#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) )
+#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) )
+#endif
+
+#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE)
+#define STATIC_REQUIRE( ... )       static_assert(   __VA_ARGS__,  #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ )
+#define STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" )
+#else
+#define STATIC_REQUIRE( ... )       REQUIRE( __VA_ARGS__ )
+#define STATIC_REQUIRE_FALSE( ... ) REQUIRE_FALSE( __VA_ARGS__ )
+#endif
+
+#endif
+
+#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature )
+
+// "BDD-style" convenience wrappers
+#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ )
+#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ )
+
+#define GIVEN( desc )     INTERNAL_CATCH_DYNAMIC_SECTION( "    Given: " << desc )
+#define AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc )
+#define WHEN( desc )      INTERNAL_CATCH_DYNAMIC_SECTION( "     When: " << desc )
+#define AND_WHEN( desc )  INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc )
+#define THEN( desc )      INTERNAL_CATCH_DYNAMIC_SECTION( "     Then: " << desc )
+#define AND_THEN( desc )  INTERNAL_CATCH_DYNAMIC_SECTION( "      And: " << desc )
+
+using Catch::Detail::Approx;
+
+#else // CATCH_CONFIG_DISABLE
+
+//////
+// If this config identifier is defined then all CATCH macros are prefixed with CATCH_
+#ifdef CATCH_CONFIG_PREFIX_ALL
+
+#define CATCH_REQUIRE( ... )        (void)(0)
+#define CATCH_REQUIRE_FALSE( ... )  (void)(0)
+
+#define CATCH_REQUIRE_THROWS( ... ) (void)(0)
+#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0)
+#define CATCH_REQUIRE_THROWS_WITH( expr, matcher )     (void)(0)
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0)
+#endif// CATCH_CONFIG_DISABLE_MATCHERS
+#define CATCH_REQUIRE_NOTHROW( ... ) (void)(0)
+
+#define CATCH_CHECK( ... )         (void)(0)
+#define CATCH_CHECK_FALSE( ... )   (void)(0)
+#define CATCH_CHECKED_IF( ... )    if (__VA_ARGS__)
+#define CATCH_CHECKED_ELSE( ... )  if (!(__VA_ARGS__))
+#define CATCH_CHECK_NOFAIL( ... )  (void)(0)
+
+#define CATCH_CHECK_THROWS( ... )  (void)(0)
+#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0)
+#define CATCH_CHECK_THROWS_WITH( expr, matcher )     (void)(0)
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0)
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+#define CATCH_CHECK_NOTHROW( ... ) (void)(0)
+
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CATCH_CHECK_THAT( arg, matcher )   (void)(0)
+
+#define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0)
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+
+#define CATCH_INFO( msg )    (void)(0)
+#define CATCH_WARN( msg )    (void)(0)
+#define CATCH_CAPTURE( msg ) (void)(0)
+
+#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
+#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
+#define CATCH_METHOD_AS_TEST_CASE( method, ... )
+#define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0)
+#define CATCH_SECTION( ... )
+#define CATCH_DYNAMIC_SECTION( ... )
+#define CATCH_FAIL( ... ) (void)(0)
+#define CATCH_FAIL_CHECK( ... ) (void)(0)
+#define CATCH_SUCCEED( ... ) (void)(0)
+
+#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
+
+#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) )
+#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className )
+#else
+#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) )
+#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) )
+#endif
+
+// "BDD-style" convenience wrappers
+#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
+#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className )
+#define CATCH_GIVEN( desc )
+#define CATCH_AND_GIVEN( desc )
+#define CATCH_WHEN( desc )
+#define CATCH_AND_WHEN( desc )
+#define CATCH_THEN( desc )
+#define CATCH_AND_THEN( desc )
+
+#define CATCH_STATIC_REQUIRE( ... )       (void)(0)
+#define CATCH_STATIC_REQUIRE_FALSE( ... ) (void)(0)
+
+// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required
+#else
+
+#define REQUIRE( ... )       (void)(0)
+#define REQUIRE_FALSE( ... ) (void)(0)
+
+#define REQUIRE_THROWS( ... ) (void)(0)
+#define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0)
+#define REQUIRE_THROWS_WITH( expr, matcher ) (void)(0)
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0)
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+#define REQUIRE_NOTHROW( ... ) (void)(0)
+
+#define CHECK( ... ) (void)(0)
+#define CHECK_FALSE( ... ) (void)(0)
+#define CHECKED_IF( ... ) if (__VA_ARGS__)
+#define CHECKED_ELSE( ... ) if (!(__VA_ARGS__))
+#define CHECK_NOFAIL( ... ) (void)(0)
+
+#define CHECK_THROWS( ... )  (void)(0)
+#define CHECK_THROWS_AS( expr, exceptionType ) (void)(0)
+#define CHECK_THROWS_WITH( expr, matcher ) (void)(0)
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0)
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+#define CHECK_NOTHROW( ... ) (void)(0)
+
+#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)
+#define CHECK_THAT( arg, matcher ) (void)(0)
+
+#define REQUIRE_THAT( arg, matcher ) (void)(0)
+#endif // CATCH_CONFIG_DISABLE_MATCHERS
+
+#define INFO( msg ) (void)(0)
+#define WARN( msg ) (void)(0)
+#define CAPTURE( msg ) (void)(0)
+
+#define TEST_CASE( ... )  INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
+#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
+#define METHOD_AS_TEST_CASE( method, ... )
+#define REGISTER_TEST_CASE( Function, ... ) (void)(0)
+#define SECTION( ... )
+#define DYNAMIC_SECTION( ... )
+#define FAIL( ... ) (void)(0)
+#define FAIL_CHECK( ... ) (void)(0)
+#define SUCCEED( ... ) (void)(0)
+#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ))
+
+#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
+#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) )
+#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className )
+#else
+#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) ) )
+#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), className ) )
+#endif
+
+#define STATIC_REQUIRE( ... )       (void)(0)
+#define STATIC_REQUIRE_FALSE( ... ) (void)(0)
+
+#endif
+
+#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature )
+
+// "BDD-style" convenience wrappers
+#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) )
+#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className )
+
+#define GIVEN( desc )
+#define AND_GIVEN( desc )
+#define WHEN( desc )
+#define AND_WHEN( desc )
+#define THEN( desc )
+#define AND_THEN( desc )
+
+using Catch::Detail::Approx;
+
+#endif
+
+#endif // ! CATCH_CONFIG_IMPL_ONLY
+
+// start catch_reenable_warnings.h
+
+
+#ifdef __clang__
+#    ifdef __ICC // icpc defines the __clang__ macro
+#        pragma warning(pop)
+#    else
+#        pragma clang diagnostic pop
+#    endif
+#elif defined __GNUC__
+#    pragma GCC diagnostic pop
+#endif
+
+// end catch_reenable_warnings.h
+// end catch.hpp
+#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
+
diff --git a/components/streams/test/filestream_unit.cpp b/components/streams/test/filestream_unit.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..54895357e8a29cb8b4a49672b1d6e7872127f9ee
--- /dev/null
+++ b/components/streams/test/filestream_unit.cpp
@@ -0,0 +1,124 @@
+#include "catch.hpp"
+
+#include <ftl/streams/filestream.hpp>
+
+using ftl::stream::File;
+using ftl::stream::Stream;
+using ftl::config::json_t;
+
+TEST_CASE("ftl::stream::File write and read", "[stream]") {
+	json_t global = json_t{{"$id","ftl://test"}};
+	ftl::config::configure(global);
+
+	json_t cfg = json_t{
+		{"$id","ftl://test/1"}
+	};
+	json_t cfg2 = json_t{
+		{"$id","ftl://test/2"}
+	};
+
+	auto *reader = ftl::create<File>(cfg);
+	REQUIRE(reader);
+	auto *writer = ftl::create<File>(cfg2);
+	REQUIRE(writer);
+
+	SECTION("write read single packet") {
+		writer->set("filename", "/tmp/ftl_file_stream_test.ftl");
+		writer->setMode(File::Mode::Write);
+
+		REQUIRE( writer->begin() );
+
+		REQUIRE( writer->post({4,ftl::timer::get_time(),2,1,ftl::codecs::Channel::Confidence},{ftl::codecs::codec_t::Any, ftl::codecs::definition_t::Any, 0, 0, 0, {'f'}}) );
+
+		writer->end();
+
+		reader->set("filename", "/tmp/ftl_file_stream_test.ftl");
+
+		ftl::codecs::StreamPacket tspkt = {4,0,0,1,ftl::codecs::Channel::Colour};
+		REQUIRE( reader->onPacket([&tspkt](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+			tspkt = spkt;
+		}) );
+		REQUIRE( reader->begin(false) );
+
+		//reader->tick();
+
+		//REQUIRE( tspkt.timestamp == 0 );
+		REQUIRE( tspkt.streamID == (uint8_t)2 );
+		REQUIRE( tspkt.channel == ftl::codecs::Channel::Confidence );
+	}
+
+	SECTION("write read multiple packets at same timestamp") {
+		writer->set("filename", "/tmp/ftl_file_stream_test.ftl");
+		writer->setMode(File::Mode::Write);
+
+		REQUIRE( writer->begin() );
+
+		REQUIRE( writer->post({4,0,0,1,ftl::codecs::Channel::Confidence},{ftl::codecs::codec_t::Any, ftl::codecs::definition_t::Any, 0, 0, 0, {'f'}}) );
+		REQUIRE( writer->post({4,0,1,1,ftl::codecs::Channel::Depth},{ftl::codecs::codec_t::Any, ftl::codecs::definition_t::Any, 0, 0, 0, {'f'}}) );
+		REQUIRE( writer->post({4,0,2,1,ftl::codecs::Channel::Screen},{ftl::codecs::codec_t::Any, ftl::codecs::definition_t::Any, 0, 0, 0, {'f'}}) );
+
+		writer->end();
+
+		reader->set("filename", "/tmp/ftl_file_stream_test.ftl");
+
+		ftl::codecs::StreamPacket tspkt = {4,0,0,1,ftl::codecs::Channel::Colour};
+		int count = 0;
+		REQUIRE( reader->onPacket([&tspkt,&count](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+			tspkt = spkt;
+			++count;
+		}) );
+		REQUIRE( reader->begin(false) );
+
+		//reader->tick();
+
+		REQUIRE( count == 3 );
+		REQUIRE( tspkt.timestamp == 0 );
+		REQUIRE( tspkt.streamID == 2 );
+		REQUIRE( tspkt.channel == ftl::codecs::Channel::Screen );
+	}
+
+	SECTION("write read multiple packets at different timestamps") {
+		writer->set("filename", "/tmp/ftl_file_stream_test.ftl");
+		writer->setMode(File::Mode::Write);
+
+		REQUIRE( writer->begin() );
+
+		auto time = ftl::timer::get_time();
+		REQUIRE( writer->post({4,time,0,1,ftl::codecs::Channel::Confidence},{ftl::codecs::codec_t::Any, ftl::codecs::definition_t::Any, 0, 0, 0, {'f'}}) );
+		REQUIRE( writer->post({4,time+ftl::timer::getInterval(),0,1,ftl::codecs::Channel::Depth},{ftl::codecs::codec_t::Any, ftl::codecs::definition_t::Any, 0, 0, 0, {'f'}}) );
+		REQUIRE( writer->post({4,time+2*ftl::timer::getInterval(),0,1,ftl::codecs::Channel::Screen},{ftl::codecs::codec_t::Any, ftl::codecs::definition_t::Any, 0, 0, 0, {'f'}}) );
+
+		writer->end();
+
+		reader->set("filename", "/tmp/ftl_file_stream_test.ftl");
+
+		ftl::codecs::StreamPacket tspkt = {4,0,0,1,ftl::codecs::Channel::Colour};
+		int count = 0;
+		REQUIRE( reader->onPacket([&tspkt,&count](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+			tspkt = spkt;
+			++count;
+		}) );
+		REQUIRE( reader->begin(false) );
+
+		//reader->tick();
+
+		REQUIRE( count == 1 );
+		//REQUIRE( tspkt.timestamp == 0 );
+		auto itime = tspkt.timestamp;
+
+		count = 0;
+		reader->tick();
+		std::this_thread::sleep_for(std::chrono::milliseconds(10));
+
+		REQUIRE( count == 1 );
+		REQUIRE( tspkt.timestamp == itime+ftl::timer::getInterval() );
+
+		count = 0;
+		reader->tick();
+		std::this_thread::sleep_for(std::chrono::milliseconds(10));
+
+		REQUIRE( count == 1 );
+		REQUIRE( tspkt.timestamp == itime+2*ftl::timer::getInterval() );
+		
+	}
+}
diff --git a/components/streams/test/netstream_unit.cpp b/components/streams/test/netstream_unit.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..33fbeb4c95029cf7c6900dcfb724038fdecff889
--- /dev/null
+++ b/components/streams/test/netstream_unit.cpp
@@ -0,0 +1,47 @@
+#include "catch.hpp"
+
+#define _FTL_NET_UNIVERSE_HPP_
+
+#include <ftl/uuid.hpp>
+
+// Mock the net universe class
+
+namespace ftl {
+namespace net {
+
+class Peer {
+
+};
+
+class Universe {
+	public:
+};
+
+}
+}
+
+#include <ftl/streams/netstream.hpp>
+#include "../src/netstream.cpp"
+
+using ftl::stream::Net;
+using ftl::stream::Stream;
+using ftl::config::json_t;
+
+TEST_CASE("ftl::stream::Net post", "[stream]") {
+	json_t global = json_t{{"$id","ftl://test"}};
+	ftl::config::configure(global);
+
+	json_t cfg = json_t{
+		{"$id","ftl://test/1"}
+	};
+
+	ftl::net::Universe mocknet;
+
+	Net *s = ftl::create<Net>(cfg, &mocknet);
+	s->set("uri", "ftl://dummy");
+
+	REQUIRE(s->begin());
+
+	delete s;
+}
+
diff --git a/components/streams/test/stream_unit.cpp b/components/streams/test/stream_unit.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a34fb73ce0098564cfeab0d45efe1e05437f935a
--- /dev/null
+++ b/components/streams/test/stream_unit.cpp
@@ -0,0 +1,239 @@
+#include "catch.hpp"
+
+#include <ftl/streams/stream.hpp>
+
+using ftl::stream::Muxer;
+using ftl::stream::Broadcast;
+using ftl::stream::Stream;
+using ftl::config::json_t;
+
+class TestStream : public ftl::stream::Stream {
+	public:
+	TestStream(nlohmann::json &config) : ftl::stream::Stream(config) {};
+	~TestStream() {};
+
+	bool onPacket(const std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> &cb) {
+		cb_ = cb;
+		return true;
+	}
+
+	bool post(const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+		available(spkt.streamID) += spkt.channel;
+		if (cb_) cb_(spkt, pkt);
+		return true;
+	}
+
+	bool begin() override { return true; }
+	bool end() override { return true; }
+	bool active() override { return true; }
+
+	private:
+	std::function<void(const ftl::codecs::StreamPacket &, const ftl::codecs::Packet &)> cb_;
+};
+
+TEST_CASE("ftl::stream::Muxer()::write", "[stream]") {
+	json_t global = json_t{{"$id","ftl://test"}};
+	ftl::config::configure(global);
+
+	json_t cfg = json_t{
+		{"$id","ftl://test/1"}
+	};
+
+	Muxer *mux = ftl::create<Muxer>(cfg);
+	REQUIRE(mux);
+
+	SECTION("write with one stream") {
+		json_t cfg = json_t{
+			{"$id","ftl://test/2"}
+		};
+
+		Stream *s = ftl::create<TestStream>(cfg);
+		REQUIRE(s);
+
+		mux->add(s);
+
+		ftl::codecs::StreamPacket tspkt = {4,0,0,1,ftl::codecs::Channel::Colour};;
+
+		s->onPacket([&tspkt](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+			tspkt = spkt;
+		});
+
+		REQUIRE( !mux->post({4,100,0,1,ftl::codecs::Channel::Colour},{}) );
+		REQUIRE( tspkt.timestamp == 0 );
+	}
+
+	SECTION("write to previously read") {
+		json_t cfg1 = json_t{
+			{"$id","ftl://test/2"}
+		};
+		json_t cfg2 = json_t{
+			{"$id","ftl://test/3"}
+		};
+
+		Stream *s1 = ftl::create<TestStream>(cfg1);
+		REQUIRE(s1);
+		Stream *s2 = ftl::create<TestStream>(cfg2);
+		REQUIRE(s2);
+
+		mux->add(s1);
+		mux->add(s2);
+
+		ftl::codecs::StreamPacket tspkt = {4,0,0,1,ftl::codecs::Channel::Colour};
+		mux->onPacket([&tspkt](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+			tspkt = spkt;
+		});
+
+		REQUIRE( s1->post({4,100,0,0,ftl::codecs::Channel::Colour},{}) );
+		REQUIRE( tspkt.timestamp == 100 );
+		REQUIRE( tspkt.frame_number == 0 );
+
+		REQUIRE( s2->post({4,101,0,0,ftl::codecs::Channel::Colour},{}) );
+		REQUIRE( tspkt.timestamp == 101 );
+		REQUIRE( tspkt.frame_number == 1 );
+
+		ftl::codecs::StreamPacket tspkt2 = {4,0,0,1,ftl::codecs::Channel::Colour};
+		ftl::codecs::StreamPacket tspkt3 = {4,0,0,1,ftl::codecs::Channel::Colour};
+		s1->onPacket([&tspkt2](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+			tspkt2 = spkt;
+		});
+		s2->onPacket([&tspkt3](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+			tspkt3 = spkt;
+		});
+
+		REQUIRE( mux->post({4,200,1,1,ftl::codecs::Channel::Colour},{}) );
+		REQUIRE( tspkt3.timestamp == 200 );
+		REQUIRE( tspkt3.frame_number == 0 );
+	}
+}
+
+TEST_CASE("ftl::stream::Muxer()::read", "[stream]") {
+	json_t global = json_t{{"$id","ftl://test"}};
+	ftl::config::configure(global);
+
+	json_t cfg = json_t{
+		{"$id","ftl://test/1"}
+	};
+
+	Muxer *mux = ftl::create<Muxer>(cfg);
+	REQUIRE(mux);
+
+	SECTION("read with two writing streams") {
+		json_t cfg1 = json_t{
+			{"$id","ftl://test/2"}
+		};
+		json_t cfg2 = json_t{
+			{"$id","ftl://test/3"}
+		};
+
+		Stream *s1 = ftl::create<TestStream>(cfg1);
+		REQUIRE(s1);
+		Stream *s2 = ftl::create<TestStream>(cfg2);
+		REQUIRE(s2);
+
+		mux->add(s1);
+		mux->add(s2);
+
+		ftl::codecs::StreamPacket tspkt = {4,0,0,1,ftl::codecs::Channel::Colour};
+		mux->onPacket([&tspkt](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+			tspkt = spkt;
+		});
+
+		REQUIRE( s1->post({4,100,0,0,ftl::codecs::Channel::Colour},{}) );
+		REQUIRE( tspkt.timestamp == 100 );
+		REQUIRE( tspkt.frame_number == 0 );
+
+		REQUIRE( s2->post({4,101,0,0,ftl::codecs::Channel::Colour},{}) );
+		REQUIRE( tspkt.timestamp == 101 );
+		REQUIRE( tspkt.frame_number == 1 );
+
+		REQUIRE( s1->post({4,102,0,1,ftl::codecs::Channel::Colour},{}) );
+		REQUIRE( tspkt.timestamp == 102 );
+		REQUIRE( tspkt.frame_number == 2 );
+
+		REQUIRE( s2->post({4,103,0,1,ftl::codecs::Channel::Colour},{}) );
+		REQUIRE( tspkt.timestamp == 103 );
+		REQUIRE( tspkt.frame_number == 3 );
+	}
+
+	SECTION("read consistency with two writing streams") {
+		json_t cfg1 = json_t{
+			{"$id","ftl://test/2"}
+		};
+		json_t cfg2 = json_t{
+			{"$id","ftl://test/3"}
+		};
+
+		Stream *s1 = ftl::create<TestStream>(cfg1);
+		REQUIRE(s1);
+		Stream *s2 = ftl::create<TestStream>(cfg2);
+		REQUIRE(s2);
+
+		mux->add(s1);
+		mux->add(s2);
+
+		ftl::codecs::StreamPacket tspkt = {4,0,0,1,ftl::codecs::Channel::Colour};
+		mux->onPacket([&tspkt](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+			tspkt = spkt;
+		});
+
+		REQUIRE( s1->post({4,100,0,0,ftl::codecs::Channel::Colour},{}) );
+		REQUIRE( tspkt.timestamp == 100 );
+		REQUIRE( tspkt.frame_number == 0 );
+
+		REQUIRE( s2->post({4,101,0,0,ftl::codecs::Channel::Colour},{}) );
+		REQUIRE( tspkt.timestamp == 101 );
+		REQUIRE( tspkt.frame_number == 1 );
+
+		REQUIRE( s1->post({4,102,0,0,ftl::codecs::Channel::Colour},{}) );
+		REQUIRE( tspkt.timestamp == 102 );
+		REQUIRE( tspkt.frame_number == 0 );
+
+		REQUIRE( s2->post({4,103,0,0,ftl::codecs::Channel::Colour},{}) );
+		REQUIRE( tspkt.timestamp == 103 );
+		REQUIRE( tspkt.frame_number == 1 );
+	}
+}
+
+TEST_CASE("ftl::stream::Broadcast()::write", "[stream]") {
+	json_t global = json_t{{"$id","ftl://test"}};
+	ftl::config::configure(global);
+
+	json_t cfg = json_t{
+		{"$id","ftl://test/1"}
+	};
+
+	Broadcast *mux = ftl::create<Broadcast>(cfg);
+	REQUIRE(mux);
+
+	SECTION("write with two streams") {
+		json_t cfg1 = json_t{
+			{"$id","ftl://test/2"}
+		};
+		json_t cfg2 = json_t{
+			{"$id","ftl://test/3"}
+		};
+
+		Stream *s1 = ftl::create<TestStream>(cfg1);
+		REQUIRE(s1);
+		Stream *s2 = ftl::create<TestStream>(cfg2);
+		REQUIRE(s2);
+
+		mux->add(s1);
+		mux->add(s2);
+
+		ftl::codecs::StreamPacket tspkt1 = {4,0,0,1,ftl::codecs::Channel::Colour};
+		ftl::codecs::StreamPacket tspkt2 = {4,0,0,1,ftl::codecs::Channel::Colour};
+
+		s1->onPacket([&tspkt1](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+			tspkt1 = spkt;
+		});
+		s2->onPacket([&tspkt2](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+			tspkt2 = spkt;
+		});
+
+		REQUIRE( mux->post({4,100,0,1,ftl::codecs::Channel::Colour},{}) );
+		REQUIRE( tspkt1.timestamp == 100 );
+		REQUIRE( tspkt2.timestamp == 100 );
+	}
+
+}
diff --git a/components/streams/test/tests.cpp b/components/streams/test/tests.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..178916eab8b9c7aabb87ff99894b48443ad6ecb6
--- /dev/null
+++ b/components/streams/test/tests.cpp
@@ -0,0 +1,3 @@
+#define CATCH_CONFIG_MAIN
+#include "catch.hpp"
+
diff --git a/components/streams/test/transcoder_unit.cpp b/components/streams/test/transcoder_unit.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391