/*
 * \brief  Device Random filesystem
 * \author Josef Soentgen
 * \date   2012-07-31
 */

/*
 * Copyright (C) 2012-2013 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.
 */

#ifndef _NOUX__RANDOM_FILE_SYSTEM_H_
#define _NOUX__RANDOM_FILE_SYSTEM_H_

/* Genode includes */
#include <base/printf.h>
#include <base/stdint.h>
#include <util/string.h>

/* Noux includes */
#include <noux_session/sysio.h>
#include "file_system.h"

/*-
 * Copyright (c) 2010, 2012
 *      Thorsten Glaser <tg@mirbsd.org>
 * Copyright (c) 2012
 *      Josef Soentgen <cnuke@mirbsd.org>
 *
 * Provided that these terms and disclaimer and all copyright notices
 * are retained or reproduced in an accompanying document, permission
 * is granted to deal in this work without restriction, including un-
 * limited rights to use, publicly perform, distribute, sell, modify,
 * merge, give away, or sublicence.
 *
 * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
 * the utmost extent permitted by applicable law, neither express nor
 * implied; without malicious intent or gross negligence. In no event
 * may a licensor, author or contributor be held liable for indirect,
 * direct, other damage, loss, or other issues arising in any way out
 * of dealing in the work, even if advised of the possibility of such
 * damage or existence of a defect, except proven that it results out
 * of said person's immediate fault when using the work as intended.
 *-
 * arc4random for use as NOUX random device.
 *
 * From:
 * MirOS: src/kern/c/arcfour_base.c,v 1.1 2010/09/12 17:10:49 tg Exp
 * MirOS: src/kern/c/arcfour_ksa.c,v 1.1 2010/09/12 17:10:50 tg Exp
 * MirOS: src/lib/libc/crypt/arc4random_base.c,v 1.4 2011/07/06 22:22:09 tg Exp
 * MirOS: src/lib/libc/crypt/arc4random_buf.c,v 1.1 2010/09/12 17:10:53 tg Exp
 */


namespace Noux {

	using namespace Genode;

	/**
	 * Arcfour cipher re-implementation from (alledged) spec description.
	 */

	class Arc4random
	{
		private:

			uint8_t S[256];
			uint8_t i;
			uint8_t j;
			uint16_t num;
			uint8_t initialised;

			/*
			 * Base cipher operation: initialise state
			 */
			void init(void)
			{
				register uint8_t n = 0;

				do {
					S[n] = n;
				} while (++n);

				i = j = 0;
			}

			/*
			 * Base cipher operation: get byte of keystream.
			 */
			uint8_t byte(void)
			{
				register uint8_t si, sj;

				si = S[++i];
				j += si;
				sj = S[j];
				S[i] = sj;
				S[j] = si;
				return (S[(uint8_t)(si + sj)]);
			}

			/*
			 * Normal key scheduling algorithm.
			 */
			void ksa(const uint8_t *key, size_t keylen)
			{
				register uint8_t si, n = 0;

				--i;
				do {
					++i;
					si = S[i];
					j = (uint8_t)(j + si + key[n++ % keylen]);
					S[i] = S[j];
					S[j] = si;
				} while (n);
				j = ++i;
			}

			/*
			 * arc4random implementation
			 */
			void stir_unlocked(void)
			{
				register unsigned int m;
				uint8_t n;
				struct {
					//	struct timeval tv;
					//	pid_t mypid;
					uint32_t mypid;
					const void *stkptr, *bssptr, *txtptr;
					uint16_t num;
					uint8_t initialised;
					// FIXME sizeof (sb) should be as close to 256 as possible
				} sb;

				/* save some state; while not a secret, helps through variety */
				//sb.mypid = getpid();
				sb.mypid = 42;
				sb.stkptr = &sb;
				sb.bssptr = &i;
				//sb.txtptr = &byte;
				sb.txtptr = (const void *)0xDEADBEEF;;
				sb.num = num;
				sb.initialised = initialised;

				/* initialise i, j and the S-box if not done yet */
				if (!initialised) {
					init();
					initialised = 1;
				}

				// FIXME initialize more sb member

				/* dance around by some bytes for added protection */
				n = byte();
				/* and carry some over to below */
				m = byte();
				while (n--)
					(void)byte();
				m += byte();

				/* while time is not a secret, it helps through variety */
				//gettimeofday(&sb.tv, NULL);

				/* actually add the hopefully random-containing seed */
				ksa((const uint8_t *)&sb, sizeof(sb));

				/* throw away the first part of the arcfour keystream */
				/* with some bytes varied for added protection */
				m += 256 * 4 + (byte() & 0x1F);
				while (m--)
					(void)byte();
				/* state is now good for so many bytes: (not so much in NOUX) */
				num = 2000;
			}

			void buf(void *buf_, size_t len)
			{
				size_t chunk;
				uint8_t *buf = (uint8_t *)buf_;
				uint8_t m, n;

				/* operate in chunks of at most 256 bytes */
				while ((chunk = len > 256 ? 256 : len) > 0) {
					/* adjust len */
					len -= chunk;

					/* is the state good for this? (or even initialised, yet?) */
					if (num < chunk)
						stir_unlocked();

					/* dance around a few bytes for added protection */
					m = byte() & 3;
					/* and carry some down below */
					n = byte() & 3;
					while (m--)
						(void)byte();

					/* actually read out the keystream into buf */
					while (chunk--) {
						*buf++ = byte();
					}

					/* dance around the bytes read from above, for protection */
					while (n--)
						(void)byte();
				}
			}

		public:

			Arc4random(void* bytes, size_t nbytes)
			:
				i(0),
				j(0),
				num(0),
				initialised(0)
			{
				memset(S, 0, 256);
			}

			void get(void *_buf, size_t len)
			{
				buf(_buf, len);
			}
	};


	class Random_file_system : public File_system
	{
		private:

			Arc4random *_arc4random;

			const char *_filename() { return "urandom"; }

			bool _is_root(const char *path)
			{
				return (strcmp(path, "") == 0) || (strcmp(path, "/") == 0);
			}

			bool _is_device_random_file(const char *path)
			{
				return (strlen(path) == (strlen(_filename()) + 1)) &&
					   (strcmp(&path[1], _filename()) == 0);
			}

		public:

			Random_file_system(void *bytes, size_t nbytes)
			{
				_arc4random = new (env()->heap()) Arc4random(bytes, nbytes);
			}

			~Random_file_system()
			{
				destroy(env()->heap(), _arc4random);
			}


			/*********************************
			 ** Directory-service interface **
			 *********************************/

			Dataspace_capability dataspace(char const *path)
			{
				/* not supported */
				return Dataspace_capability();
			}

			void release(char const *path, Dataspace_capability ds_cap)
			{
				/* not supported */
			}

			bool stat(Sysio *sysio, char const *path)
			{
				Sysio::Stat st = { 0, 0, 0, 0, 0, 0 };

				if (_is_root(path))
					st.mode = Sysio::STAT_MODE_DIRECTORY;
				else if (_is_device_random_file(path)) {
					st.mode = Sysio::STAT_MODE_CHARDEV;
				} else {
					sysio->error.stat = Sysio::STAT_ERR_NO_ENTRY;
					return false;
				}

				sysio->stat_out.st = st;
				return true;
			}

			bool dirent(Sysio *sysio, char const *path, off_t index)
			{
				if (_is_root(path)) {
					if (index == 0) {
						sysio->dirent_out.entry.type = Sysio::DIRENT_TYPE_CHARDEV;
						strncpy(sysio->dirent_out.entry.name,
						        _filename(),
						        sizeof(sysio->dirent_out.entry.name));
					} else {
						sysio->dirent_out.entry.type = Sysio::DIRENT_TYPE_END;
					}

					return true;
				}

				return false;
			}

			size_t num_dirent(char const *path)
			{
				if (_is_root(path))
					return 1;
				else
					return 0;
			}

			bool is_directory(char const *path)
			{
				if (_is_root(path))
					return true;

				return false;
			}

			char const *leaf_path(char const *path)
			{
				return path;
			}

			Vfs_handle *open(Sysio *sysio, char const *path)
			{
				if (!_is_device_random_file(path)) {
					sysio->error.open = Sysio::OPEN_ERR_UNACCESSIBLE;
					return 0;
				}

				return new (env()->heap()) Vfs_handle(this, this, 0);
			}

			bool unlink(Sysio *sysio, char const *path)
			{
				/* not supported */
				return false;
			}

			bool readlink(Sysio *sysio, char const *path)
			{
				/* not supported */
				return false;
			}

			bool rename(Sysio *sysio, char const *from_path, char const *to_path)
			{
				/* not supported */
				return false;
			}

			bool mkdir(Sysio *sysio, char const *path)
			{
				/* not supported */
				return false;
			}

			bool symlink(Sysio *sysio, char const *path)
			{
				/* not supported */
				return false;
			}

			/***************************
			 ** File_system interface **
			 ***************************/

			char const *name() const { return "random"; }


			/********************************
			 ** File I/O service interface **
			 ********************************/

			bool write(Sysio *sysio, Vfs_handle *handle)
			{
				sysio->write_out.count = sysio->write_in.count;

				return true;
			}

			bool read(Sysio *sysio, Vfs_handle *vfs_handle)
			{
				size_t nbytes = sysio->read_in.count;

				_arc4random->get(sysio->read_out.chunk, nbytes);
				sysio->read_out.count = nbytes;

				return true;
			}

			bool ftruncate(Sysio *sysio, Vfs_handle *vfs_handle) { return true; }
	};
}

#endif /* _NOUX__RANDOM_H_ */
