//-------------------------------------------------------------------------------------------------
// <copyright file="EncryptedPayloadController.cpp" company="LiquidRobotics">
//  Copyright (c) Liquid Robotics Corporation, confidential and proprietary.  All rights reserved. 
// </copyright>
//
// <summary>
// </summary>
//
// <owner></owner>
//-------------------------------------------------------------------------------------------------

#include "EncryptedPayloadController.h"
#include "LqrSMACCmdLineParser.h"
#include "LqrSMACGetErrorDescription.h"
#include "LqrSMACPipes.h"
#include "LqrSMACPrintfBuffer.h"
#include "nsextend.h"
#include <fstream>
#include <string>
#include <vector>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <boost/algorithm/string.hpp>

using namespace std;
using namespace LiquidRobotics::SMACFramework;
using namespace LiquidRobotics::PayloadInterface;
using namespace LiquidRobotics::PayloadInterface::FCCMessage;

typedef PayloadPortSlave Base;

namespace LiquidRobotics {
namespace EncryptedStartup {

EncryptedPayloadController::EncryptedPayloadController(const EncryptedStartupSettings& _settings, Logger& _logger, Semaphore& _runSem)
: Base(_settings.GetPortSettings(), _logger)
, config(_settings)
, runSem(_runSem)
, halt(false)
, shutdownCompleted(false)
, enqueueStartupTelem(true)
{
}

EncryptedPayloadController::~EncryptedPayloadController()
{
}

// Overriding this handler to catch the exit from the program
void EncryptedPayloadController::Run(HDLCPort::Ptr pHDLC)
{
	PayloadPortSlave::Run(pHDLC);

	if (!shutdownCompleted)
		try 		{ HandleShutdown(); }
		catch(...) 	{ }
}

// Overriding this handler to halt the system when requested
PayloadMessage::Ptr EncryptedPayloadController::WaitForMessage(HDLCPort::Ptr pHDLC)
{
	if (halt)
		HandleShutdown();

	bool b1 = !!action;
	bool b2 = Base::HaveMessagesQueued(config.GetBoardAddress());
	//Logger::Locked(GetLogger())->Debug << "WaitForMessage: " << b1 << " " << b2 << " " << (b1 ? action->havePassPhrase : -1) << endl;

	if (b1 && !b2)
		ExecutePostCryptoAction();

	return Base::WaitForMessage(pHDLC);
}

// This method is called whenever an ASCII- or binary-formatted console command is received
void EncryptedPayloadController::OnConsoleMessage(
		BoardAddress source,
		BoardAddress target,
		const Level2Wrapper& receivedLevel2,
		const LiquidRobotics::PayloadInterface::FCCMessage::CMD_FMT_1E& receivedParams,
		const ByteArray& dataBytes,
		bool asciiFormat,
		bool& acked,
		uint8_t& nakDisp,
		uint8_t& nakSummary,
		bool wantsAck)
{
	{
		Logger::Locked lockedLog(GetLogger());

		lockedLog->Info
			<< "OnConsoleMessage (" << (asciiFormat ? "ASCII" : "binary") << ")"
			<< " wantsAck=" << wantsAck
			<< " source=0x" << utos(source)
			<< " dest=0x" << utos(receivedParams.dest,16,4)
			<< " function=0x" << utos(receivedParams.function,16,2)
			<< " dataLength=" << receivedParams.dataLength
			<< " data=";

		if (asciiFormat)
		{
			string dataStr;

			if (dataBytes.size())
				dataStr.assign((const char*) &dataBytes.front(), dataBytes.size());

			lockedLog->Info << "\"" << dataStr << "\"" << endl;
		}
		else if (GetLogger().LogEnabled(Logger::LT_DEBUG))
			lockedLog->Info << dataBytes.ToString() << endl;
		else
			lockedLog->Info << dataBytes.size() << " bytes" << endl;
	}

	if (!asciiFormat || (receivedParams.function != 0))
	{
		acked = false;
		nakDisp = CONSOLE_RESPONSE_UNSUPPORTED_CMD;
	}
	else
	{
		string dataStr;

		if (dataBytes.size())
			dataStr.assign((const char*) &dataBytes.front(), dataBytes.size());

		if (receivedParams.dest == config.GetBoardAddress())
		{
			bool good = true;
			CmdLineParser::Ptr parser;

			try
			{
				parser.reset(new CmdLineParser(dataStr));
			}
			catch(exception& ex)
			{
				good = false;
				acked = true;

				stringstream msg;
				msg << "Caught " << typeid(ex).name() << " \"" << ex.what() << "\"";

				string msg2 = msg.str();
				EnqueueConsoleResponse(msg2, receivedLevel2.commHeader.packetID, receivedParams.dest, source);
			}

			if (good)
			{
				const int tokenCount = parser->Count();
				const CmdLineParser::StringArray& tokens = parser->GetTokens();

				if (tokenCount && boost::iequals(tokens[0], "CRYPTO"))
				{
					acked = true;
					GenericCrypto(*parser, receivedLevel2.commHeader.packetID, source);
				}
				else if (tokenCount && boost::iequals(tokens[0], "CRYPTMNT"))
				{
					acked = true;
					CryptoMount(*parser, receivedLevel2.commHeader.packetID, source);
				}
				else if (tokenCount && boost::iequals(tokens[0], "RUNSHELL"))
				{
					acked = true;
					RunShell(*parser, receivedLevel2.commHeader.packetID, source);
				}
				else if (tokenCount)
				{
					good = false;
					acked = true;

					stringstream msg;
					msg << "Unrecognized command \"" << tokens[0] << "\"";

					string msg2 = msg.str();
					EnqueueConsoleResponse(msg2, receivedLevel2.commHeader.packetID, receivedParams.dest, source);
				}
				else
				{
					good = false;
					acked = true;
					EnqueueConsoleResponse(string("Not enough command tokens"), receivedLevel2.commHeader.packetID, receivedParams.dest, source);
				}
				/*else
				{
					acked = true;
					EnqueueConsoleResponse(string("OK"), receivedLevel2.commHeader.packetID, receivedParams.dest, source);
				}*/
			}
		}
		else
		{
			acked = false;
			nakDisp = CONSOLE_RESPONSE_UNKNOWN_DESTINATION;
			nakSummary = PayloadProtocol::NAKSUMMARY_DEVICE_NOT_FOUND;
		}
	}
}

void EncryptedPayloadController::EnqueueConsoleResponse(const string& data, uint32_t packetID, uint16_t source, uint16_t dest)
{
	const ByteArray::byte* pStart = (const ByteArray::byte*) data.data();
	ByteArray bytes(pStart, pStart+data.length());

	EnqueueConsoleResponse(bytes, packetID, source, dest);
}

void EncryptedPayloadController::EnqueueConsoleResponse(const ByteArray& data, uint32_t packetID, uint16_t source, uint16_t dest)
{
	Logger::Locked(GetLogger())->Info << "EnqueueConsoleResponse packetID=0x" << ultos(packetID, 16, 8)
									  << " 0x" << utos(source,16,4) << " -> 0x" << utos(dest,16,4)
									  << " data=" << data.ToStringSplitView() << endl;

	const uint16_t dataSize = data.size();
	const uint16_t maxReturnedSize = 256;
	uint16_t blockSize;
	bool sizeOverflow;

	if ((blockSize = dataSize) > maxReturnedSize)
	{
		blockSize = maxReturnedSize;
		sizeOverflow = true;
	}
	else
		sizeOverflow = false;

	ConsoleAck consoleAck(0, sizeOverflow ? CONSOLE_RESPONSE_OK_TRUNCATED : CONSOLE_RESPONSE_OK,
			ByteArray(data.begin(), data.begin()+blockSize));
	PPSByteStream bsLevel1;
	bsLevel1 << consoleAck;

	PPSByteStream bs;
	bs << PayloadProtocol::LRIGenericAck(0, false)
	   << ForwardInfo(UNSOLICITED_SHIP_TO_SHORE, dest, source)
	   << Level2Wrapper(ACK_CONSOLE_DATA, packetID, bsLevel1.AsBytes());

	PayloadMessage::Ptr payloadMessage;
	payloadMessage.reset(new PayloadMessage(PayloadMessage::LRI_ACK, source, dest, 0, bs.AsBytes()));

	Logger::Locked(GetLogger())->Info << "(" << data.size() << " bytes) Enqueuing message " << *payloadMessage << endl;

	EnqueueMessage(payloadMessage);
}

// This methnod is called whenever a power notification message is received; the user should fill out either
// the ack responses or the nak container
void EncryptedPayloadController::OnPowerNotification(
		const PayloadProtocol::PYLD_POWER_NOTIFICATION& notification,
		PayloadProtocol::ACK_PYLD_POWER_NOTIFICATION_RECORD& ack,
		bool& acked,
		PayloadProtocol::ENakSummary& nakSummary)
{
	ack.address = MainTaskAddress();
	ack.boardID = GetBoardID(MainTaskAddress());
	ack.state = 1;
	ack.delayOff = config.GetPowerOffDelay();
	ack.delayOn = config.GetPowerOnDelay();
	acked = true;

	if (config.GetShutdownVerb().length())
	{
		Logger::Locked(GetLogger())->Info << "Setting halt flag" << endl;
		halt = true;
	}
	else
		Logger::Locked(GetLogger())->Info << "Not actually halting since shutdown verb is empty" << endl;
}

// This method is called by the default OnMessageReceived implementation when an ID/enumeration request is
// received; the user should fill out the responses array for processing on return.
void EncryptedPayloadController::OnRequestIDReceived(const PayloadProtocol::LRI_REQUEST_ID& idRequest,
		LRI_ID_PACKET_ARRAY& responses)
{
	responses.resize(1);

	memset(&responses.at(0), '\0', sizeof(PayloadProtocol::LRI_ID_PACKET));

	responses[0].address = config.GetBoardAddress();

	const string::size_type descriptionSize = sizeof(responses[0].description);
	char* description = responses[0].description;
	strncpy(description, config.GetDescription().c_str(), descriptionSize);
	description[descriptionSize-1] = '\0';

	responses[0].deviceType = config.GetDeviceType();
	responses[0].firmwareMajor = config.GetFirmwareMajor();
	responses[0].firmwareMinor = config.GetFirmwareMinor();
	responses[0].firmwareRev = config.GetFirmwareRev();
	responses[0].pollingRate = config.GetPollingRate();
	responses[0].port = 0;

	const string::size_type serNumBufSize = sizeof(responses[0].serialNum);
	char* serialNum = responses[0].serialNum;
	memset(serialNum, ' ', serNumBufSize);
	const string& serNum = config.GetSerialNum();
	const string::size_type serNumLen = serNum.length();
	memcpy(serialNum, serNum.c_str(), min(serNumLen, serNumBufSize));

	responses[0].version = 1;

	if (config.GetTelemetryRequested())				responses[0].extraInfo |= PayloadProtocol::EXTRA_INFO_TELEMETRY;
	if (config.GetPowerNotificationRequested())		responses[0].extraInfo |= PayloadProtocol::EXTRA_INFO_POWER;
	if (config.GetEventsRequested())				responses[0].extraInfo |= PayloadProtocol::EXTRA_INFO_EVENTS;
	if (config.GetEchoedAcksRequested())			responses[0].extraInfo |= PayloadProtocol::EXTRA_INFO_ECHOED_ACKS;

	EnqueueStartupTelemetryMessage(true);
}

// This method is called by the default OnMessageReceived implementation when a status request message is
// received; the user should fill out either the Nak structure or the Ack strcture
void EncryptedPayloadController::OnStatusRequest(
		const PayloadProtocol::LRI_REQUEST_PIB_STATUS& statusRequest,
		PayloadProtocol::LRI_PIB_STATUS& ack,
		bool& acked,
		PayloadProtocol::ENakSummary& nakSummary)
{
	LatLon fix;
	const bool validFix = (statusRequest.fixValid == 'Y');

	if (validFix)
	{
		fix = LatLon(statusRequest.latitude, statusRequest.longitude);
		lastFix = fix;

		Logger::Locked(GetLogger())->Info
			 << "Valid fix at "
			 << PrintfBuffer("%.4u/%.2u/%.2u %.2u:%.2u:%.2u UTC",
					 2000 + (int) statusRequest.timestamp.year,
					 statusRequest.timestamp.month,
					 statusRequest.timestamp.day,
					 statusRequest.timestamp.hour,
					 statusRequest.timestamp.minute,
					 statusRequest.timestamp.second)
			 << " at " << fix << endl;

		// set the time and date
		string cmd = config.GetDateVerbGMT(
				2000 + (int) statusRequest.timestamp.year,
				statusRequest.timestamp.month,
				statusRequest.timestamp.day,
				statusRequest.timestamp.hour,
				statusRequest.timestamp.minute,
				statusRequest.timestamp.second);

		if (cmd.length())
		{
			Logger::Locked(GetLogger())->Info << "cmd: " << cmd << endl;

			const int systemReturnCode = system(cmd.c_str());
			if (systemReturnCode);
		}
		else
			Logger::Locked(GetLogger())->Debug << "There is no configured verb to set the date/time" << endl;
	}
	else
		Logger::Locked(GetLogger())->Info << "Incoming fix is not valid" << endl;

	// queue up startup telemetry if flag is set
	if (DeferredStartupTelem())
		EnqueueStartupTelemetryMessage();

	ZeroObject(ack);
	ack.version = 1;
	acked = true;
}

// Returns true if there are messages queued to be read out by the Host, false if there are none; this method
// is used to set the "have messages" flag in all responses
bool EncryptedPayloadController::HaveMessagesQueued(BoardAddress sourceAddress) const
{
	return Base::HaveMessagesQueued(sourceAddress);
}

// This method should return true unless intending to exit
bool EncryptedPayloadController::Running() const
{
	return !halt && !shutdownCompleted;
}

// Returns true if the address is one that should be Nak'd, false if the address is unknown
bool EncryptedPayloadController::SendNaksForAddress(BoardAddress destinationAddress) const
{
	return destinationAddress != 0xFFFF;
}

// Returns the board address of the main task
PayloadMessage::BoardAddress EncryptedPayloadController::MainTaskAddress() const
{
	return config.GetBoardAddress();
}

void EncryptedPayloadController::HandleShutdown()
{
	ShutdownSystem();
	shutdownCompleted = true;
	runSem.Wait();
}

void EncryptedPayloadController::ShutdownSystem()
{
	Logger::Locked(GetLogger())->Info << "Halt action pending" << endl;

	string shutdownVerb = config.GetShutdownVerb();

	if (shutdownVerb.length())
	{
		Logger::Locked(GetLogger())->Info << "cmd: " << shutdownVerb << endl;
		const int systemReturnCode = system(shutdownVerb.c_str());
		if (systemReturnCode);
	}
	else
		Logger::Locked(GetLogger())->Info << "No defined shutdown verb" << endl;
}

EncryptedPayloadController::Logger& EncryptedPayloadController::GetLogger() const
{
	return Base::logger;
}

void EncryptedPayloadController::EnqueueStartupTelemetryMessage(bool defer)
{
	if (defer)
	{
		enqueueStartupTelem = true;
		Logger::Locked(GetLogger())->Debug << "Deferred startup set" << endl;
	}
	else
	{
		Logger::Locked(GetLogger())->Debug << "Reporting " << (enqueueStartupTelem ? "deferred " : "") << " startup telemetry" << endl;
		enqueueStartupTelem = false;

		// --- set up the telemetry message ---
		TELEMETRY_91 telem;
		telem.Set(SYSINFO_SMC_PARTITION_STATE, config.GetBoardAddress(), SYSINFOSMCPARTITIONSTATE_SUBTYPE_0);

		TELEMETRY_91_SAMPLE0 sample0;

		if (lastFix.IsValid())
		{
			sample0.latitude = lastFix.GetLatitude().GetNumeric();
			sample0.longitude = lastFix.GetLongitude().GetNumeric();
		}
		else
			sample0.latitude = sample0.longitude = 0;

		sample0.sampleTime_T = time(NULL);

		PPSByteStream bsLevel1;
		bsLevel1 << telem
		         << sample0
		         << (uint8_t) SYSINFOSMCPARTITIONSTATEVALUE_DISABLED;

		PPSByteStream bs;
		bs << PayloadProtocol::LRIGenericAck(0, false)
		   << Level2Wrapper(SND_SYSTEM_INFO, bsLevel1.AsBytes(), Level2Wrapper::WITHOUT_CRC, true); // FSReport

		// --- now stuff the payload packet bytes into a message (transaction ID set to 0 here, will be corrected later) ---
		PayloadMessage::Ptr payloadMessage;
		payloadMessage.reset(new PayloadMessage(PayloadMessage::LRI_ACK, config.GetBoardAddress(), FLOAT_TARGET_IRIDIUM_INTERFACE, 0, bs.AsBytes()));

		// --- now queue up the message for transmission later ---
		Logger::Locked(GetLogger())->Info << "Enqueuing startup telemetry message " << *payloadMessage << endl;
		EnqueueMessage(payloadMessage);
	}
}

bool EncryptedPayloadController::DeferredStartupTelem() const
{
	return enqueueStartupTelem;
}

void EncryptedPayloadController::GenericCrypto(const CmdLineParser& parser, uint32_t packetID, uint16_t responseTarget)
{
	//EnqueueConsoleResponse(string("CRYPTO command not supported yet"), packetID, config.GetBoardAddress(), responseTarget);
}

void EncryptedPayloadController::CryptoMount(const CmdLineParser& parser, uint32_t packetID, uint16_t responseTarget)
{
	//EnqueueConsoleResponse(string("CRYPTMNT command not supported yet"), packetID, config.GetBoardAddress(), responseTarget);
	const int tokenCount = parser.Count();
	const CmdLineParser::StringArray& tokens = parser.GetTokens();

	if (tokenCount < 7)
	{
		stringstream msg;
		msg << tokens[0] << ": not enough arguments";

		string msg2 = msg.str();
		EnqueueConsoleResponse(msg2, packetID, config.GetBoardAddress(), responseTarget);
		return;
	}

	const string& passphrase = tokens[1];
	const string& passphraseExpand = tokens[2];
	const string& partitionDevice = tokens[3];
	const string& cryptoDevice = tokens[4];
	const string& mountPoint = tokens[5];

	if (!passphrase.length())
	{
		stringstream msg;
		msg << tokens[0] << ": nil pass phrase";

		string msg2 = msg.str();
		EnqueueConsoleResponse(msg2, packetID, config.GetBoardAddress(), responseTarget);
		return;
	}

	struct stat statbuf1;
	const int status1 = stat(partitionDevice.c_str(), &statbuf1);
	const int err1 = errno;

	if (status1)
	{
		stringstream msg;
		msg << tokens[0] << ": cannot access partition device, " << partitionDevice << " (" << GetErrorDescription(err1) << ")";
		string msg2 = msg.str();
		EnqueueConsoleResponse(msg2, packetID, config.GetBoardAddress(), responseTarget);
		return;
	}

	else if (!S_ISBLK(statbuf1.st_mode))
	{
		stringstream msg;
		msg << tokens[0] << ": partition device (" << partitionDevice << ") is expected to be a block device";
		string msg2 = msg.str();
		EnqueueConsoleResponse(msg2, packetID, config.GetBoardAddress(), responseTarget);
		return;
	}

	if (AlreadyMounted(mountPoint))
	{
		stringstream msg;
		msg << tokens[0] << ": " << mountPoint << " is already mounted";
		string msg2 = msg.str();
		EnqueueConsoleResponse(msg2, packetID, config.GetBoardAddress(), responseTarget);
		return;
	}

	string moreCryptoInfo;
	bool cryptoInUse = CryptoUsed(cryptoDevice, moreCryptoInfo);

	if (cryptoInUse)
	{
		stringstream msg;
		msg << tokens[0] << ": " << moreCryptoInfo;
		string msg2 = msg.str();
		EnqueueConsoleResponse(msg2, packetID, config.GetBoardAddress(), responseTarget);
		return;
	}

	string passEx;

	if (passphraseExpand.compare("-") && passphraseExpand.length())
	{
		Logger::Locked(GetLogger())->Info << "Expanding pass phrase" << endl;

		int passstat = PipeRun(passphraseExpand, passphrase, passEx);

		if (passstat != 0)
		{
			stringstream msg;
			msg << tokens[0] << ": passphrase expansion failed with code " << passstat;
			string msg2 = msg.str();
			EnqueueConsoleResponse(msg2, packetID, config.GetBoardAddress(), responseTarget);
			return;
		}
	}
	else
		passEx = passphrase;

	Logger::Locked(GetLogger())->Info << "Creating encryption device" << endl;

	CmdLineParser::StringArray cmdTokens;

	cmdTokens.push_back("cryptsetup");
	cmdTokens.push_back("2>&1");
	cmdTokens.push_back("create");
	cmdTokens.push_back("--cipher");
	cmdTokens.push_back("aes-cbc-essiv:sha256");
	cmdTokens.push_back("--key-size");
	cmdTokens.push_back("256");
	cmdTokens.push_back("--hash");
	cmdTokens.push_back("sha256");
	cmdTokens.push_back(cryptoDevice);
	cmdTokens.push_back(partitionDevice);

	const string cryptCmd = CmdLineParser::MakeCmdLine(cmdTokens);
	string cryptOut;
	int cryptStatus = PipeRun(cryptCmd, passEx, cryptOut);

	if (cryptStatus != 0)
	{
		Logger::Locked(GetLogger())->Error << "Crypto setup failed with code " << cryptStatus << endl
										   << "Output = " << cryptOut << endl;
		stringstream msg;
		msg << tokens[0] << ": crypto setup failed with code " << cryptStatus;
		string msg2 = msg.str();
		EnqueueConsoleResponse(msg2, packetID, config.GetBoardAddress(), responseTarget);
		return;
	}

	Logger::Locked(GetLogger())->Info << "Mounting encrypted partition" << endl;

	cmdTokens.clear();
	cmdTokens.push_back("mount");
	cmdTokens.push_back(CryptoDevicePath(cryptoDevice));
	cmdTokens.push_back(mountPoint);

	const string mountCmd = CmdLineParser::MakeCmdLine(cmdTokens);
	int mountStatus = system(mountCmd.c_str());

	if (mountStatus != 0)
	{
		Logger::Locked(GetLogger())->Error << "Mount command failed with code " << mountStatus << endl;
		stringstream msg;
		msg << tokens[0] << ": mount command failed with code " << mountStatus;
		string msg2 = msg.str();
		EnqueueConsoleResponse(msg2, packetID, config.GetBoardAddress(), responseTarget);
		return;
	}

	EnqueueConsoleResponse(string("OK"), packetID, config.GetBoardAddress(), responseTarget);
	cmdTokens.assign(tokens.begin()+6, tokens.end());

	Logger::Locked(GetLogger())->Info << "Post crypto action created" << endl;
	action.reset(new CryptoAction(CmdLineParser::MakeCmdLine(cmdTokens), config.GetBoardAddress(), responseTarget, packetID));
}

int EncryptedPayloadController::PipeRun(const string& cmd, const string& input, string& output)
{
	Logger::Locked(GetLogger())->Debug << "PipeRun(" << cmd << ", " << input << ")" << endl;
	Pipes stage1;
	pid_t child1 = fork();

	if (child1)				// same process
	{
		stage1.CloseWritePipe();
		Pipes::MakeCloseOnExec(stage1.ReadPipe());

		Pipes stage2;
		pid_t child2 = fork();

		if (child2)			// same process
		{
			stage2.CloseWritePipe();

			char buffer[16384];
			int readOffset = 0;
			int readStatus;

			do {

				readStatus = read(stage2.ReadPipe(), buffer+readOffset, sizeof(buffer)-readOffset);

				 if (readStatus > 0)
					 readOffset += readStatus;

			} while((readStatus > 0) && (readOffset < (int) sizeof(buffer)));

			if (readOffset > 0)
			{
				output.assign(buffer, buffer+readOffset);

				if (readOffset == (int) sizeof(buffer))
					do
					{
						readStatus = read(stage2.ReadPipe(), buffer, sizeof(buffer));
					} while(readStatus == (int) sizeof(buffer));
			}

			int result1;
			/*pid_t status1 = */waitpid(child1, &result1, 0);
			//cout << "child1 = " << result1 << endl;

			int result2;
			/*pid_t status2 = */waitpid(child2, &result2, 0);
			//cout << "child2 = " << result2 << endl;

			int returnValue;

			if (WIFEXITED(result2))
			{
				returnValue = WEXITSTATUS(result2);
				Logger::Locked(GetLogger())->Debug << "PipeRun: exit status " << returnValue << endl;
			}
			else if (WIFSIGNALED(result2))
			{
				returnValue = -WTERMSIG(result2);
				Logger::Locked(GetLogger())->Debug << "PipeRun: terminated by signal " << -returnValue << endl;
			}
			else
			{
				returnValue = -100;
				Logger::Locked(GetLogger())->Debug << "PipeRun: unexpected exit (-100)" << endl;
			}

			return returnValue;
		}
		else				// child process that executes the command
		{
			stage2.CloseReadPipe();
			dup2(stage1.ReadPipe(), 0);
			stage1.Close();
			dup2(stage2.WritePipe(), 1);
			dup2(stage2.WritePipe(), 2);
			stage2.Close();

			//cout << "Running execlp for " << cmd << endl;
			execlp("sh", "sh", "-c", cmd.c_str(), (const char*) NULL);
			//cout << "After sh" << endl;
			exit(7);
		}
	}
	else					// child process that writes the passphrase
	{
		stage1.CloseReadPipe();
		write(stage1.WritePipe(), input.data(), input.length());
		stage1.Close();
		//cout << "Exiting writer" << endl;
		exit(0);
	}
}

int EncryptedPayloadController::CloseCrypto(const string& cryptoDevice)
{
	stringstream cmd;
	cmd << "sync ; umount " << CryptoDevicePath(cryptoDevice) << " ; cryptsetup remove " << cryptoDevice;
	const string cmd2 = cmd.str();
	return system(cmd2.c_str());
}

bool EncryptedPayloadController::CryptoUsed(const string& cryptoDevice, string& info)
{
	const string cryptoDevicePath = CryptoDevicePath(cryptoDevice);

	struct stat statbuf;
	const int status = stat(cryptoDevicePath.c_str(), &statbuf);
	const int err = errno;

	if (status && (err == ENOENT))
		return false;

	else if (status)
	{
		stringstream msg;
		msg << "error accessing crypto device (" << GetErrorDescription(err) << ")";
		info = msg.str();
		return true;
	}

	else
	{
		stringstream msg;
		msg << "crypto device file (" << cryptoDevicePath << ") exists";
		info = msg.str();
		return true;
	}
}

string EncryptedPayloadController::CryptoDevicePath(const string& cryptoDevice)
{
	stringstream tmp;
	tmp << "/dev/mapper/" << cryptoDevice;
	return tmp.str();
}

void EncryptedPayloadController::ExecutePostCryptoAction()
{
	if (!action->havePassPhrase)
	{
		Logger::Locked(GetLogger())->Debug << "Running post crypto action: " << action->cmd << endl;
		execlp("sh", "sh", "-c", action->cmd.c_str(), NULL);

		// error ... shouldn't have returned
		const int err = errno;
		stringstream msg;

		msg << "Failed to execute post-crypto action: " << GetErrorDescription(err);

		string msg2 = msg.str();
		EnqueueConsoleResponse(msg2, action->packetID, action->src, action->dst);

		action.reset();
	}
}

bool EncryptedPayloadController::AlreadyMounted(const string& mountPoint) const
{
	bool result = false;

	try
	{
		ifstream ifs("/proc/mounts");
		string line;

		while(!result && (getline(ifs, line), ifs.good()))
		{
			vector<string> strings;
			boost::split(strings, line, boost::is_any_of("\t "));

			if (strings.size() == 6)
				if (!mountPoint.compare(strings[1]))
					result = true;
		}
	}
	catch(...)
	{
	}

	return result;
}

void EncryptedPayloadController::RunShell(const CmdLineParser& parser, uint32_t packetID, uint16_t responseTarget)
{

	EnqueueConsoleResponse(string("OK"), packetID, config.GetBoardAddress(), responseTarget);

	const int tokenCount = parser.Count();
	const CmdLineParser::StringArray& tokens = parser.GetTokens();

	if (tokenCount < 2)
	{
		stringstream msg;
		msg << tokens[0] << ": not enough arguments";

		string msg2 = msg.str();
		EnqueueConsoleResponse(msg2, packetID, config.GetBoardAddress(), responseTarget);
		return;
	}

	CmdLineParser::StringArray cmdTokens;

	cmdTokens.assign(tokens.begin()+1, tokens.end());

	Logger::Locked(GetLogger())->Info << "Post crypto action created" << endl;
	action.reset(new CryptoAction(CmdLineParser::MakeCmdLine(cmdTokens), config.GetBoardAddress(), responseTarget, packetID));
}

}
}
