/*
 * File:	elftosb.cpp
 *
 * Copyright (c) Freescale Semiconductor, Inc. All rights reserved.
 * See included license file for license details.
 */

#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <stdlib.h>
#include <stdexcept>
#include "ConversionController.h"
#include "options.h"
#include "Version.h"
#include "EncoreBootImage.h"
#include "smart_ptr.h"
#include "Logging.h"
#include "EncoreBootImageGenerator.h"
#include "SearchPath.h"
#include "format_string.h"

//! An array of strings.
typedef std::vector<std::string> string_vector_t;

//! The tool's name.
const char k_toolName[] = "elftosb";

//! Current version number for the tool.
const char k_version[] = "2.6.1";

//! Copyright string.
const char k_copyright[] = "Copyright (c) 2004-2010 Freescale Semiconductor, Inc.\nAll rights reserved.";

static const char * k_optionsDefinition[] = {
	"?|help",
	"v|version",
	"f:chip-family <family>",
	"c:command <file>",
	"o:output <file>",
	"P:product <version>",
	"C:component <version>",
	"k:key <file>",
	"z|zero-key",
	"D:define <const>",
	"O:option <option>",
	"d|debug",
	"q|quiet",
	"V|verbose",
	"p:search-path <path>",
	NULL
};

//! Help string.
const char k_usageText[] = "\nOptions:\n\
  -?/--help                    Show this help\n\
  -v/--version                 Display tool version\n\
  -f/--chip-family <family>    Select the chip family (default is 37xx)\n\
  -c/--command <file>          Use this command file\n\
  -o/--output <file>           Write output to this file\n\
  -p/--search-path <path>      Add a search path used to find input files\n\
  -P/--product <version        Set product version\n\
  -C/--component <version>     Set component version\n\
  -k/--key <file>              Add OTP key, enable encryption\n\
  -z/--zero-key                Add default key of all zeroes\n\
  -D/--define <const>=<int>    Define or override a constant value\n\
  -O/--option <name>=<value>   Set or override a processing option\n\
  -d/--debug                   Enable debug output\n\
  -q/--quiet                   Output only warnings and errors\n\
  -V/--verbose                 Print extra detailed log information\n\n";

// prototypes
int main(int argc, char* argv[], char* envp[]);

/*!
 * \brief Class that encapsulates the elftosb tool.
 *
 * A single global logger instance is created during object construction. It is
 * never freed because we need it up to the last possible minute, when an
 * exception could be thrown.
 */
class elftosbTool
{
protected:
	//! Supported chip families.
	enum chip_family_t
	{
		k37xxFamily,	//!< 37xx series.
		kMX28Family,	//!< Catskills series.
	};
	
	/*!
	 * \brief A structure describing an entry in the table of chip family names.
	 */
	struct FamilyNameTableEntry
	{
		const char * const name;
		chip_family_t family;
	};
	
	//! \brief Table that maps from family name strings to chip family constants.
	static const FamilyNameTableEntry kFamilyNameTable[];
	
	int m_argc;							//!< Number of command line arguments.
	char ** m_argv;						//!< String value for each command line argument.
	StdoutLogger * m_logger;			//!< Singleton logger instance.
	string_vector_t m_keyFilePaths;		//!< Paths to OTP key files.
	string_vector_t m_positionalArgs;	//!< Arguments coming after explicit options.
	bool m_isVerbose;					//!< Whether the verbose flag was turned on.
	bool m_useDefaultKey;					//!< Include a default (zero) crypto key.
	const char * m_commandFilePath;		//!< Path to the elftosb command file.
	const char * m_outputFilePath;		//!< Path to the output .sb file.
	const char * m_searchPath;			//!< Optional search path for input files.
	elftosb::version_t m_productVersion;	//!< Product version specified on command line.
	elftosb::version_t m_componentVersion;	//!< Component version specified on command line.
	bool m_productVersionSpecified;		//!< True if the product version was specified on the command line.
	bool m_componentVersionSpecified;		//!< True if the component version was specified on the command line.
	chip_family_t m_family;				//!< Chip family that the output file is formatted for.
	elftosb::ConversionController m_controller;	//!< Our conversion controller instance.
		
public:
	/*!
	 * Constructor.
	 *
	 * Creates the singleton logger instance.
	 */
	elftosbTool(int argc, char * argv[])
	:	m_argc(argc),
		m_argv(argv),
		m_logger(0),
		m_keyFilePaths(),
		m_positionalArgs(),
		m_isVerbose(false),
		m_useDefaultKey(false),
		m_commandFilePath(NULL),
		m_outputFilePath(NULL),
		m_searchPath(NULL),
		m_productVersion(),
		m_componentVersion(),
		m_productVersionSpecified(false),
		m_componentVersionSpecified(false),
		m_family(k37xxFamily),
		m_controller()
	{
		// create logger instance
		m_logger = new StdoutLogger();
		m_logger->setFilterLevel(Logger::INFO);
		Log::setLogger(m_logger);
	}
	
	/*!
	 * Destructor.
	 */
	~elftosbTool()
	{
	}
	
	/*!
	 * \brief Searches the family name table.
	 *
	 * \retval true The \a name was found in the table, and \a family is valid.
	 * \retval false No matching family name was found. The \a family argument is not modified.
	 */
	bool lookupFamilyName(const char * name, chip_family_t * family)
	{
		// Create a local read-write copy of the argument string.
		std::string familyName(name);
		
		// Convert the argument string to lower case for case-insensitive comparison.
		for (int n=0; n < familyName.length(); n++)
		{
			familyName[n] = tolower(familyName[n]);
		}
		
        // Exit the loop if we hit the NULL terminator entry.
		const FamilyNameTableEntry * entry = &kFamilyNameTable[0];
		for (; entry->name; entry++)
		{
			// Compare lowercased name with the table entry.
			if (familyName == entry->name)
			{
				*family = entry->family;
				return true;
			}
		}
		
		// Failed to find a matching name.
		return false;
	}
	
	/*!
	 * Reads the command line options passed into the constructor.
	 *
	 * This method can return a return code to its caller, which will cause the
	 * tool to exit immediately with that return code value. Normally, though, it
	 * will return -1 to signal that the tool should continue to execute and
	 * all options were processed successfully.
	 *
	 * The Options class is used to parse command line options. See
	 * #k_optionsDefinition for the list of options and #k_usageText for the
	 * descriptive help for each option.
	 *
	 * \retval -1 The options were processed successfully. Let the tool run normally.
	 * \return A zero or positive result is a return code value that should be
	 *		returned from the tool as it exits immediately.
	 */
	int processOptions()
	{
		Options options(*m_argv, k_optionsDefinition);
		OptArgvIter iter(--m_argc, ++m_argv);
		
		// process command line options
		int optchar;
		const char * optarg;
		while (optchar = options(iter, optarg))
		{
			switch (optchar)
			{
				case '?':
					printUsage(options);
					return 0;
				
				case 'v':
					printf("%s %s\n%s\n", k_toolName, k_version, k_copyright);
					return 0;
				
				case 'f':
					if (!lookupFamilyName(optarg, &m_family))
					{
						Log::log(Logger::ERROR, "error: unknown chip family '%s'\n", optarg);
						printUsage(options);
						return 0;
					}
					break;
					
				case 'c':
					m_commandFilePath = optarg;
					break;
					
				case 'o':
					m_outputFilePath = optarg;
					break;
					
				case 'P':
					m_productVersion.set(optarg);
					m_productVersionSpecified = true;
					break;
					
				case 'C':
					m_componentVersion.set(optarg);
					m_componentVersionSpecified = true;
					break;
					
				case 'k':
					m_keyFilePaths.push_back(optarg);
					break;
				
				case 'z':
					m_useDefaultKey = true;
					break;
					
				case 'D':
					overrideVariable(optarg);
					break;

				case 'O':
					overrideOption(optarg);
					break;
					
				case 'd':
					Log::getLogger()->setFilterLevel(Logger::DEBUG);
					break;
					
				case 'q':
					Log::getLogger()->setFilterLevel(Logger::WARNING);
					break;
					
				case 'V':
					m_isVerbose = true;
					break;
				
				case 'p':
				{
					std::string newSearchPath(optarg);
					PathSearcher::getGlobalSearcher().addSearchPath(newSearchPath);
					break;
				}
					
				default:
					Log::log(Logger::ERROR, "error: unrecognized option\n\n");
					printUsage(options);
					return 0;
			}
		}
		
		// handle positional args
		if (iter.index() < m_argc)
		{
			Log::SetOutputLevel leveler(Logger::DEBUG);
			Log::log("positional args:\n");
			int i;
			for (i = iter.index(); i < m_argc; ++i)
			{
				Log::log("%d: %s\n", i - iter.index(), m_argv[i]);
				m_positionalArgs.push_back(m_argv[i]);
			}
		}
		
		// all is well
		return -1;
	}

	/*!
	 * Prints help for the tool.
	 */
	void printUsage(Options & options)
	{
		options.usage(std::cout, "files...");
		printf("%s", k_usageText);
	}
	
	/*!
	 * \brief Core of the tool.
	 *
	 * Calls processOptions() to handle command line options before performing the
	 * real work the tool does.
	 */
	int run()
	{
		try
		{
			// read command line options
			int result;
			if ((result = processOptions()) != -1)
			{
				return result;
			}
			
			// set verbose logging
			setVerboseLogging();
			
			// check argument values
			checkArguments();

			// set up the controller
			m_controller.setCommandFilePath(m_commandFilePath);
			
			// add external paths to controller
			string_vector_t::iterator it = m_positionalArgs.begin();
			for (; it != m_positionalArgs.end(); ++it)
			{
				m_controller.addExternalFilePath(*it);
			}
			
			// run conversion
			convert();
		}
		catch (std::exception & e)
		{
			Log::log(Logger::ERROR, "error: %s\n", e.what());
			return 1;
		}
		catch (...)
		{
			Log::log(Logger::ERROR, "error: unexpected exception\n");
			return 1;
		}
		
		return 0;
	}
	
	/*!
	 * \brief Validate arguments that can be checked.
	 * \exception std::runtime_error Thrown if an argument value fails to pass validation.
	 */
	void checkArguments()
	{
		if (m_commandFilePath == NULL)
		{
			throw std::runtime_error("no command file was specified");
		}
		if (m_outputFilePath == NULL)
		{
			throw std::runtime_error("no output file was specified");
		}
	}
	
	/*!
	 * \brief Turns on verbose logging.
	 */
	void setVerboseLogging()
	{
		if (m_isVerbose)
		{
			// verbose only affects the INFO and DEBUG filter levels
			// if the user has selected quiet mode, it overrides verbose
			switch (Log::getLogger()->getFilterLevel())
			{
				case Logger::INFO:
					Log::getLogger()->setFilterLevel(Logger::INFO2);
					break;
				case Logger::DEBUG:
					Log::getLogger()->setFilterLevel(Logger::DEBUG2);
					break;
			}
		}
	}

	/*!
	 * \brief Returns the integer value for a string.
	 *
	 * Metric multiplier prefixes are supported.
	 */
	uint32_t parseIntValue(const char * value)
	{
		// Accept 'true'/'yes' and 'false'/'no' as integer values.
		if ((strcmp(value, "true") == 0) || (strcmp(value, "yes") == 0))
		{
			return 1;
		}
		else if ((strcmp(value, "false") == 0) || (strcmp(value, "no") == 0))
		{
			return 0;
		}
		
		uint32_t intValue = strtoul(value, NULL, 0);
		unsigned multiplier;
		switch (value[strlen(value) - 1])
		{
			case 'G':
				multiplier = 1024 * 1024 * 1024;
				break;
			case 'M':
				multiplier = 1024 * 1024;
				break;
			case 'K':
				multiplier = 1024;
				break;
			default:
				multiplier = 1;
		}
		intValue *= multiplier;
		return intValue;
	}
	
	/*!
	 * \brief Parses the -D option to override a constant value.
	 */
	void overrideVariable(const char * optarg)
	{
		// split optarg into two strings
		std::string constName(optarg);
		int i;
		for (i=0; i < strlen(optarg); ++i)
		{
			if (optarg[i] == '=')
			{
				constName.resize(i++);
				break;
			}
		}
		
		uint32_t constValue = parseIntValue(&optarg[i]);
		
		elftosb::EvalContext & context = m_controller.getEvalContext();
		context.setVariable(constName, constValue);
		context.lockVariable(constName);
	}

	/*!
	 * \brief
	 */
	void overrideOption(const char * optarg)
	{
		// split optarg into two strings
		std::string optionName(optarg);
		int i;
		for (i=0; i < strlen(optarg); ++i)
		{
			if (optarg[i] == '=')
			{
				optionName.resize(i++);
				break;
			}
		}
		
		// handle quotes for option value
		const char * valuePtr = &optarg[i];
		bool isString = false;
		int len;
		if (valuePtr[0] == '"')
		{
			// remember that the value is a string and get rid of the opening quote
			isString = true;
			valuePtr++;

			// remove trailing quote if present
			len = strlen(valuePtr);
			if (valuePtr[len] == '"')
			{
				len--;
			}
		}

		elftosb::Value * value;
		if (isString)
		{
			std::string stringValue(valuePtr);
			stringValue.resize(len);	// remove trailing quote
			value = new elftosb::StringValue(stringValue);
		}
		else
		{
			value = new elftosb::IntegerValue(parseIntValue(valuePtr));
		}

		// Set and lock the option in the controller
		m_controller.setOption(optionName, value);
		m_controller.lockOption(optionName);
	}
	
	/*!
	 * \brief Do the conversion.
	 * \exception std::runtime_error This exception is thrown if the conversion controller does
	 *		not produce a boot image, or if the output file cannot be opened. Other errors
	 *		internal to the conversion controller may also produce this exception.
	 */
	void convert()
	{
		// create a generator for the chosen chip family
		smart_ptr<elftosb::BootImageGenerator> generator;
		switch (m_family)
		{
			case k37xxFamily:
				generator = new elftosb::EncoreBootImageGenerator;
				elftosb::g_enableHABSupport = false;
				break;

			case kMX28Family:
				generator = new elftosb::EncoreBootImageGenerator;
				elftosb::g_enableHABSupport = true;
				break;
		}
		
		// process input and get a boot image
		m_controller.run();
		smart_ptr<elftosb::BootImage> image = m_controller.generateOutput(generator);
		if (!image)
		{
			throw std::runtime_error("failed to produce output!");
		}
		
		// set version numbers if they were provided on the command line
		if (m_productVersionSpecified)
		{
			image->setProductVersion(m_productVersion);
		}
		if (m_componentVersionSpecified)
		{
			image->setComponentVersion(m_componentVersion);
		}
		
		// special handling for each family
		switch (m_family)
		{
			case k37xxFamily:
			case kMX28Family:
			{
				// add OTP keys
				elftosb::EncoreBootImage * encoreImage = dynamic_cast<elftosb::EncoreBootImage*>(image.get());
				if (encoreImage)
				{
					// add keys
					addCryptoKeys(encoreImage);
					
					// print debug image
					encoreImage->debugPrint();
				}
				break;
			}
		}
		
		// write output
		std::ofstream outputStream(m_outputFilePath, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc);
		if (outputStream.is_open())
		{
			image->writeToStream(outputStream);
		}
		else
		{
			throw std::runtime_error(format_string("could not open output file %s", m_outputFilePath));
		}
	}
	
	/*!
	 * \brief
	 */
	void addCryptoKeys(elftosb::EncoreBootImage * encoreImage)
	{
		string_vector_t::iterator it = m_keyFilePaths.begin();
		for (; it != m_keyFilePaths.end(); ++it)
		{
			std::string & keyPath = *it;
			
			std::string actualPath;
			bool found = PathSearcher::getGlobalSearcher().search(keyPath, PathSearcher::kFindFile, true, actualPath);
			if (!found)
			{
				throw std::runtime_error(format_string("unable to find key file %s\n", keyPath.c_str()));
			}
			
			std::ifstream keyStream(actualPath.c_str(), std::ios_base::in);
			if (!keyStream.is_open())
			{
				throw std::runtime_error(format_string("unable to read key file %s\n", keyPath.c_str()));
			}
			keyStream.seekg(0);
			
			try
			{
				// read as many keys as possible from the stream
				while (true)
				{
					AESKey<128> key(keyStream);
					encoreImage->addKey(key);
					
					// dump key bytes
					dumpKey(key);
				}
			}
			catch (...)
			{
				// ignore the exception -- there are just no more keys in the stream
			}
		}
		
		// add the default key of all zero bytes if requested
		if (m_useDefaultKey)
		{
			AESKey<128> defaultKey;
			encoreImage->addKey(defaultKey);
		}
	}
	
	/*!
	 * \brief Write the value of each byte of the \a key to the log.
	 */
	void dumpKey(const AESKey<128> & key)
	{
		// dump key bytes
		Log::log(Logger::DEBUG, "key bytes: ");
		AESKey<128>::key_t the_key;
		key.getKey(&the_key);
		int q;
		for (q=0; q<16; q++)
		{
			Log::log(Logger::DEBUG, "%02x ", the_key[q]);
		}
		Log::log(Logger::DEBUG, "\n");
	}

};

const elftosbTool::FamilyNameTableEntry elftosbTool::kFamilyNameTable[] =
	{
		{ "37xx", k37xxFamily },
		{ "377x", k37xxFamily },
		{ "378x", k37xxFamily },
		{ "mx23", k37xxFamily },
		{ "imx23", k37xxFamily },
		{ "i.mx23", k37xxFamily },
		{ "mx28", kMX28Family },
		{ "imx28", kMX28Family },
		{ "i.mx28", kMX28Family },
		
		// Null terminator entry.
		{ NULL, k37xxFamily }
	};

/*!
 * Main application entry point. Creates an sbtool instance and lets it take over.
 */
int main(int argc, char* argv[], char* envp[])
{
	try
	{
		return elftosbTool(argc, argv).run();
	}
	catch (...)
	{
		Log::log(Logger::ERROR, "error: unexpected exception\n");
		return 1;
	}

	return 0;
}



