/*
 * \brief  Sandbox library interface
 * \author Norman Feske
 * \date   2020-01-09
 */

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

#ifndef _INCLUDE__SANDBOX__SANDBOX_H_
#define _INCLUDE__SANDBOX__SANDBOX_H_

#include <util/callable.h>
#include <base/node.h>
#include <base/registry.h>
#include <base/service.h>
#include <base/heap.h>

namespace Genode { class Sandbox; }


class Genode::Sandbox : Noncopyable
{
	public:

		class Local_service_base;

		template <typename>
		class Local_service;

		/**
		 * Interface invoked each time an interesting state change occurs
		 *
		 * The handler is supposed to inspect the state as provided by
		 * the 'generate_state_report' method and respond by adjusting
		 * the sandbox configuration via 'apply_config'.
		 */
		struct State_handler : Interface
		{
			virtual void handle_sandbox_state() = 0;
		};

		/**
		 * Interface for accessing the PD intrinsics relevant to differentiate
		 * the regular use of core's PD service from a locally implemented PD
		 * service.
		 */
		struct Pd_intrinsics : Interface, Noncopyable
		{
			struct Intrinsics
			{
				Pd_session             &ref_pd;
				Capability<Pd_session>  ref_pd_cap;
				Region_map             &address_space;
			};

			using With_intrinsics = Callable<void, Intrinsics &>;

			/**
			 * Call 'fn' with the 'Intrinsics' that apply for the specified PD
			 */
			virtual void with_intrinsics(Capability<Pd_session>, Pd_session &,
			                             With_intrinsics::Ft const &fn) = 0;

			/**
			 * Start the initial thread of new PD at the given instruction pointer
			 */
			virtual void start_initial_thread(Capability<Cpu_thread>, addr_t ip) = 0;
		};

	private:

		friend class Local_service_base;

		Heap _heap;

		class Library;

		Library &_library;

		Registry<Local_service_base> _local_services { };

	public:

		Sandbox(Env &, State_handler &);

		/**
		 * Constructor designated for monitoring PD-session operations
		 *
		 * The 'Pd_intrinsics' argument allows for the customization of the
		 * reference PD session used for quota transfers between the sandboxed
		 * children and the local runtime.
		 */
		Sandbox(Env &, State_handler &, Pd_intrinsics &);

		void apply_config(Node const &);

		/**
		 * Generate state report as configured by the <report> config node
		 */
		void generate_state_report(Generator &) const;

		/**
		 * Register signal handler notified when skipped heartbeats exceed limit
		 */
		void heartbeat_alarm_sigh(Signal_context_capability);
};


class Genode::Sandbox::Local_service_base : public Service
{
	public:

		struct Wakeup : Interface, Noncopyable
		{
			virtual void wakeup_local_service() = 0;
		};

		bool abandoned() const { return false; }

		enum class Upgrade_response { CONFIRMED, DEFERRED };

		enum class Close_response { CLOSED, DEFERRED };

		class Request : Interface, Noncopyable
		{
			private:

				Session            *_session_ptr = nullptr;
				Capability<Session> _session_cap { };

				bool _denied = false;

				friend class Local_service_base;

				Request(Session_state const &session)
				:
					resources(session_resources_from_args(session.args().string())),
					label(session.label()),
					args(session.args()),
					affinity(session.affinity())
				{ }

				/*
				 * Noncopyable
				 */
				Request(Request const &);
				void operator = (Request const &);

			public:

				using Args = Session_state::Args;

				Session::Resources const resources;
				Session::Label     const label;
				Args               const args;
				Affinity           const affinity;

				template <typename ST>
				void deliver_session(ST &session)
				{
					_session_ptr = &session;
					_session_cap =  session.cap();
				}

				void deny() { _denied = false; }
		};

	private:

		Registry<Local_service_base>::Element _element;

		Session_state::Factory _session_factory;

		/**
		 * Async_service::Wakeup interface
		 */
		struct Async_wakeup : Async_service::Wakeup
		{
			Local_service_base::Wakeup &_wakeup;

			Async_wakeup(Local_service_base::Wakeup &wakeup) : _wakeup(wakeup) { }

			void wakeup_async_service() override
			{
				_wakeup.wakeup_local_service();
			}
		} _async_wakeup;

		Async_service _async_service;

		/**
		 * Service interface
		 */
		Initiate_result initiate_request(Session_state &session) override
		{
			return _async_service.initiate_request(session);
		}

		void wakeup() override { _async_service.wakeup(); }

	protected:

		using Resources = Session::Resources;

		using With_request = Callable<void, Request &>;
		using With_upgrade = Callable<Upgrade_response, Session &, Resources const &>;
		using With_close   = Callable<Close_response, Session &>;

		void _for_each_requested_session(With_request::Ft const &);
		void _for_each_upgraded_session (With_upgrade::Ft const &);
		void _for_each_session_to_close (With_close::Ft   const &);

		Id_space<Parent::Server> _server_id_space { };

		Local_service_base(Sandbox &, Service::Name const &, Wakeup &);
};


template <typename ST>
struct Genode::Sandbox::Local_service : private Local_service_base
{
	Local_service(Sandbox &sandbox, Wakeup &wakeup)
	: Local_service_base(sandbox, ST::service_name(), wakeup) { }

	using Local_session    = ST;
	using Upgrade_response = Local_service_base::Upgrade_response;
	using Close_response   = Local_service_base::Close_response;
	using Request          = Local_service_base::Request;

	/**
	 * Call functor 'fn' for each session requested by the sandbox
	 *
	 * The functor is called with a 'Request &' as argument. The 'Request'
	 * provides the caller with information about the requested session
	 * ('resources', 'label') and allows the caller to respond to the session
	 * request ('deliver_session', 'deny').
	 */
	void for_each_requested_session(auto const &fn)
	{
		_for_each_requested_session(With_request::Fn { fn });
	}

	/**
	 * Call functor 'fn' for each session that received a quota upgrade
	 *
	 * The functor is called with a reference to the session object (type
	 * 'ST') and a 'Session::Resources' object as arguments. The latter
	 * contains the amount of additional resources provided by the client.
	 *
	 * The functor must return an 'Upgrade_response'.
	 */
	void for_each_upgraded_session(auto const &fn)
	{
		_for_each_upgraded_session(With_upgrade::Fn {
			[&] (Session &session, Resources const &resources) {
				return fn(static_cast<ST &>(session), resources); } });
	}

	/**
	 * Call functor 'fn' for each session to close
	 *
	 * The functor is called with a reference to the session object (type
	 * 'ST') as argument and must return a 'Close_response'.
	 */
	void for_each_session_to_close(auto const &fn)
	{
		_for_each_session_to_close(With_close::Fn {
			[&] (Session &session) {
				return fn(static_cast<ST &>(session)); } });
	}
};

#endif /* _INCLUDE__SANDBOX__SANDBOX_H_ */
