/*
 * \brief  Test program for detecting faults
 * \author Norman Feske
 * \date   2013-01-03
 */

/*
 * Copyright (C) 2008-2016 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

#include <base/log.h>
#include <base/component.h>
#include <base/child.h>
#include <rom_session/connection.h>
#include <log_session/log_session.h>
#include <loader_session/connection.h>
#include <region_map/client.h>

using namespace Genode;


template <typename TEST>
class Iterative_test
{
	public:

	private:

		Env                      &_env;
		Signal_context_capability _finished_sigh;
		unsigned            const _cnt_max = 5;
		unsigned                  _cnt     = 0;

		Signal_handler<Iterative_test> _fault_handler {
			_env.ep(), *this, &Iterative_test::_handle_fault };

		TEST _test;;

		void _handle_fault()
		{
			if (_cnt++ >= _cnt_max) {
				Signal_transmitter(_finished_sigh).submit();
				log("-- finished ", _test.name(), " --");
				return;
			}
			_test.start_iteration(_env, _fault_handler);
		}

	public:

		Iterative_test(Env &env, Signal_context_capability finished_sigh)
		:
			_env(env), _finished_sigh(finished_sigh)
		{
			log("-- exercise ", _test.name(), " --");
			_test.start_iteration(_env, _fault_handler);
		}
};


/********************************************************************
 ** Test for detecting the failure of an immediate child component **
 ********************************************************************/

class Test_child : public Genode::Child_policy
{
	private:

		Env                      &_env;
		size_t              const _ram_quota = 1024*1024;
		Binary_name         const _binary_name;
		Signal_context_capability _sigh;
		Parent_service            _cpu_service { _env, Cpu_session::service_name() };
		Parent_service            _ram_service { _env, Ram_session::service_name() };
		Parent_service            _pd_service  { _env,  Pd_session::service_name() };
		Parent_service            _log_service { _env, Log_session::service_name() };
		Parent_service            _rom_service { _env, Rom_session::service_name() };
		Child                     _child;

	public:

		/**
		 * Constructor
		 */
		Test_child(Env &env, Name const &binary_name,
		           Genode::Signal_context_capability sigh)
		:
			_env(env), _binary_name(binary_name), _sigh(sigh),
			_child(_env.rm(), _env.ep().rpc_ep(), *this)
		{ }


		/****************************
		 ** Child-policy interface **
		 ****************************/

		Name name() const override { return "child"; }

		Binary_name binary_name() const override { return _binary_name; }

		Ram_session           &ref_ram()           override { return _env.ram(); }
		Ram_session_capability ref_ram_cap() const override { return _env.ram_session_cap(); }

		void init(Ram_session &ram, Ram_session_capability ram_cap) override
		{
			ram.ref_account(ref_ram_cap());
			ref_ram().transfer_quota(ram_cap, _ram_quota);
		}

		void init(Cpu_session &cpu, Cpu_session_capability) override
		{
			/* register default exception handler */
			cpu.exception_sigh(_sigh);
		}

		void init(Pd_session &pd, Pd_session_capability) override
		{
			/* register handler for unresolvable page faults */
			Region_map_client address_space(pd.address_space());
			address_space.fault_handler(_sigh);
		}

		Service &resolve_session_request(Service::Name const &service,
		                                 Session_state::Args const &args) override
		{
			if (service == Cpu_session::service_name()) return _cpu_service;
			if (service == Ram_session::service_name()) return _ram_service;
			if (service ==  Pd_session::service_name()) return  _pd_service;
			if (service == Log_session::service_name()) return _log_service;
			if (service == Rom_session::service_name()) return _rom_service;

			throw Parent::Service_denied();
		}

		void filter_session_args(Service::Name const &,
		                         char *args, size_t args_len) override
		{
			/* prefix session label */
			Session_label const orig(label_from_args(args));
			Arg_string::set_arg_string(args, args_len, "label",
			                           prefixed_label(name(), orig).string());
		}
};


struct Faulting_child_test
{
	static char const *name() { return "failure detection in immediate child"; }

	Lazy_volatile_object<Test_child> _child;

	void start_iteration(Env &env, Signal_context_capability fault_sigh)
	{
		_child.construct(env, "test-segfault", fault_sigh);
	}
};


/******************************************************************
 ** Test for detecting failures in a child started by the loader **
 ******************************************************************/

struct Faulting_loader_child_test
{
	static char const *name() { return "failure detection in loaded child"; }

	Lazy_volatile_object<Loader::Connection> loader;

	void start_iteration(Env &env, Signal_context_capability fault_sigh)
	{
		loader.construct(env, 1024*1024);

		/* register fault handler at loader session */
		loader->fault_sigh(fault_sigh);

		/* start subsystem */
		loader->start("test-segfault");
	}
};


/***********************************************************************
 ** Test for detecting failures in a grandchild started by the loader **
 ***********************************************************************/

struct Faulting_loader_grand_child_test
{
	static char const *name() { return "failure detection of loaded grand child"; }

	static char const *config()
	{
		return
			"<config>\n"
			"  <parent-provides>\n"
			"    <service name=\"ROM\"/>\n"
			"    <service name=\"RM\"/>\n"
			"    <service name=\"LOG\"/>\n"
			"  </parent-provides>\n"
			"  <default-route>\n"
			"    <any-service> <parent/> <any-child/> </any-service>\n"
			"  </default-route>\n"
			"  <start name=\"test-segfault\">\n"
			"    <resource name=\"RAM\" quantum=\"10M\"/>\n"
			"  </start>\n"
			"</config>";
	}

	static size_t config_size() { return strlen(config()); }

	Lazy_volatile_object<Loader::Connection> loader;

	void start_iteration(Env &env, Signal_context_capability fault_sigh)
	{
		loader.construct(env, 2*1024*1024);

		/* import config into loader session */
		{
			Attached_dataspace ds(loader->alloc_rom_module("config", config_size()));
			memcpy(ds.local_addr<char>(), config(), config_size());
			loader->commit_rom_module("config");
		}

		/* register fault handler at loader session */
		loader->fault_sigh(fault_sigh);

		/* start subsystem */
		loader->start("init", "init");
	}
};


struct Main
{
	Env &_env;

	Lazy_volatile_object<Iterative_test<Faulting_child_test> >              _test_1;
	Lazy_volatile_object<Iterative_test<Faulting_loader_child_test> >       _test_2;
	Lazy_volatile_object<Iterative_test<Faulting_loader_grand_child_test> > _test_3;

	Signal_handler<Main> _test_1_finished_handler {
		_env.ep(), *this, &Main::_handle_test_1_finished };

	Signal_handler<Main> _test_2_finished_handler {
		_env.ep(), *this, &Main::_handle_test_2_finished };

	Signal_handler<Main> _test_3_finished_handler {
		_env.ep(), *this, &Main::_handle_test_3_finished };

	void _handle_test_1_finished()
	{
		_test_1.destruct();
		_test_2.construct(_env, _test_2_finished_handler);
	}

	void _handle_test_2_finished()
	{
		_test_2.destruct();
		_test_3.construct(_env, _test_3_finished_handler);
	}

	void _handle_test_3_finished()
	{
		_test_3.destruct();
		log("--- finished fault_detection test ---");
		_env.parent().exit(0);
	}

	Main(Env &env) : _env(env)
	{
		_test_1.construct(_env, _test_1_finished_handler);
	}
};


void Component::construct(Env &env) { static Main main(env); }

