//
// $Id: DefinitionAnalyzer.cpp,v 1.14 2005/09/27 19:07:27 bakerj Exp $
//
//****************************************************************************************//
// Copyright (c) 2005, The MITRE Corporation
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice, this list
//       of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice, this 
//       list of conditions and the following disclaimer in the documentation and/or other
//       materials provided with the distribution.
//     * Neither the name of The MITRE Corporation nor the names of its contributors may be
//       used to endorse or promote products derived from this software without specific 
//       prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//****************************************************************************************//

#include "DefinitionAnalyzer.h"

//****************************************************************************************//
//							DefinitionAnalyzer Class									  //	
//****************************************************************************************//
DefinitionAnalyzer *DefinitionAnalyzer::instance = NULL;

DefinitionAnalyzer::DefinitionAnalyzer() : Analyzer()
{
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	
	// -----------------------------------------------------------------------

}

DefinitionAnalyzer::~DefinitionAnalyzer()
{
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//
	// -----------------------------------------------------------------------
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Public Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
Analyzer* DefinitionAnalyzer::Instance()
{
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Force this class to be a singleton. Return an Instance of the 
	//	DefiitionAnalyzer. 
	//
	// -----------------------------------------------------------------------

	if(instance == NULL)
		instance = new DefinitionAnalyzer();

	return instance;
}

Result DefinitionAnalyzer::Run(DOMElement *definition)
{
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Createa a definition element in the results file based on this definition
	//	Evaluate the criteria and store the result values in the result
	//	vectors.
	//	Combine the result values in the result vectors 
	//	Set the result for the definition and return the result to the caller
	//
	// -----------------------------------------------------------------------

	// Get the definition Id
	string definitionId = XmlCommon::GetAttributeByName(definition, "id");
	
	// Create the definition in the result file
	DOMElement *resultDefinition = this->resultWriter->AddDefinition((DOMElement*)definition);

	// Get the Criteria node
	DOMNode *criteria = XmlCommon::FindNodeNS(definition, "criteria");
	if(criteria == NULL)
		throw AnalyzerException("Error: Unable to find the \"criteria\" element while evaluating definition " + definitionId ); 

	// Evaluate the Criteria
	Result result = EvaluateCriteria(resultDefinition, (DOMElement*)criteria);

	return result;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Private Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

Result DefinitionAnalyzer::EvaluateCriteria(DOMElement *resultDefinition, DOMElement *criteriaNode)
{
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Evaluate the criteria for the specified definition.
	//	Return the result for the criteria based on the useConfigureation flag.
	//
	// -----------------------------------------------------------------------

	// Get the definition id
	string definitionId = XmlCommon::GetAttributeByName(resultDefinition, "id");

	// debug output
	//Log::WriteLog("\t"+definitionId);
	// end debug output

	iVector configurationResults;
	iVector softwareResults;
	iVector criteriaResults;
	Result configurationResult = unknown_result;
	Result softwareResult = unknown_result;
	Result criterionResult = unknown_result;
	string test_refAttr = "";
	string negateAttr = "";
	string commentAttr = "";

	// Get a reference to the AnalyzerFactory
	AnalyzerFactory *analyzerFactory = AnalyzerFactory::Instance();

	///////////////////////////////////////////////////////////////////////////
	//	Process the software node
	///////////////////////////////////////////////////////////////////////////

	//	Get the software node
	DOMElement* software = XmlCommon::FindNodeNS(criteriaNode, "software");
	if(software != NULL) {

		// Get the operation
		string softwareOperation = XmlCommon::GetAttributeByName(software, "operation");
		if(softwareOperation.compare("") == 0)
			softwareOperation = "AND";

		//	For each cirterion determine and set the value of the result attribute
		for (DOMNode* tmpNode = software->getFirstChild(); tmpNode != NULL; tmpNode=tmpNode->getNextSibling()) {
			if(tmpNode->getNodeType() == DOMNode::ELEMENT_NODE) {
				DOMElement* criterion = (DOMElement*)tmpNode;
				try {

					//	Get test_ref attribute and validate it
					test_refAttr = XmlCommon::GetAttributeByName(criterion, "test_ref");
					if (test_refAttr.compare("") == 0)
						throw AnalyzerException("Unable to locate the test_ref attribute for the current criterion.");
					
					//	Get negate attribute and validate it
					negateAttr = XmlCommon::GetAttributeByName(criterion, "negate");
					if (negateAttr.compare("") == 0)
						negateAttr = "false";

					//	Get comment attribute and validate it
					commentAttr = XmlCommon::GetAttributeByName(criterion, "comment");
					if (commentAttr.compare("") == 0)
						commentAttr = "false";

					//	Get the corresponding test
					DOMNode *testNode = XmlCommon::FindNodeByAttribute(XmlCommon::FindNodeNS((ovalDoc)->getDocumentElement(), "tests"), "id", test_refAttr);
					if(testNode == NULL)
						throw AnalyzerException("Unable to locate the test that corresponds to the current criterion. id: " + test_refAttr);

					//	Get an Analyzer for this test
					Analyzer *testAnalyzer = analyzerFactory->GetAnalyzer((DOMElement*)testNode);

					//	Run the analyzer
					criterionResult = testAnalyzer->Run((DOMElement*)testNode);
					
					//	Write the criterion to the results file
					this->resultWriter->AddCriterion(resultDefinition, "software", negateAttr, test_refAttr, ResultToString(criterionResult), commentAttr);

					// Use the negate attribute
					if(negateAttr.compare("true") == 0) 
						criterionResult = NegateResult(criterionResult);

					//	Store the result
					softwareResults.push_back(criterionResult);
					
				} catch(Exception ex) {
					// Should this be a fatal error that stops that analyzer?

					//	Add the result attribute to the test that failed
					criterionResult = unknown_result;

					//	Store the result
					softwareResults.push_back(criterionResult);

					//	Write the criterion to the results file
					this->resultWriter->AddCriterion(resultDefinition, "software", negateAttr, test_refAttr, ResultToString(criterionResult), commentAttr);


					//	Display a message if verbose mode
					if(Log::verboseMode) {

						string msg = "     Error: An unexpected error occured while evaluating oval definition ";
						msg.append(definitionId);
						if(test_refAttr.compare("") != 0) {

							msg.append(" test_ref: ");
							msg.append(test_refAttr);
						}
						msg.append(".\n     Message: " + ex.GetErrorMessage());

						Log::WriteLog(msg);
						cout << msg << endl;
					}
				}
			}
		}

		// Combine software results
		softwareResult = CombineResultsByOperation(&softwareResults, softwareOperation);

		// Set the Software result
		this->resultWriter->SetCriterionParentResult(resultDefinition, "software", ResultToString(softwareResult));

		// Add the software result to the criteria results vector
		criteriaResults.push_back(softwareResult);
	}

	///////////////////////////////////////////////////////////////////////////
	//	Process the configuration node
	///////////////////////////////////////////////////////////////////////////

	//	Get the software node
	DOMElement* configuration = XmlCommon::FindNodeNS(criteriaNode, "configuration");
	if(configuration != NULL) {

		// Get the operation
		string configurationOperation = XmlCommon::GetAttributeByName(configuration, "operation");
		if(configurationOperation.compare("") == 0)
			configurationOperation = "AND";

		//	For each cirterion determine and set the value of the result attribute
		for (DOMNode *tmpNode = configuration->getFirstChild(); tmpNode != NULL; tmpNode=tmpNode->getNextSibling()) {
			if(tmpNode->getNodeType() == DOMNode::ELEMENT_NODE) {
				DOMElement* criterion = (DOMElement*)tmpNode;
				try {

					//	Get test_ref attribute and validate it
					test_refAttr = XmlCommon::GetAttributeByName(criterion, "test_ref");
					if (test_refAttr.compare("") == 0)
						throw AnalyzerException("Unable to locate the test_ref attribute for the current criterion.");
					
					//	Get negate attribute and validate it
					negateAttr = XmlCommon::GetAttributeByName(criterion, "negate");
					if (negateAttr.compare("") == 0)
						negateAttr = "false";

					//	Get comment attribute and validate it
					commentAttr = XmlCommon::GetAttributeByName(criterion, "comment");
					if (commentAttr.compare("") == 0)
						commentAttr = "false";

					//	Get the corresponding test
					DOMNode *testNode = XmlCommon::FindNodeByAttribute(XmlCommon::FindNodeNS((ovalDoc)->getDocumentElement(), "tests"), "id", test_refAttr);
					if(testNode == NULL)
						throw AnalyzerException("Unable to locate the test that corresponds to the current criterion. id; " + test_refAttr);

					//	Get an Analyzer for this test
					Analyzer *testAnalyzer = analyzerFactory->GetAnalyzer((DOMElement*)testNode);

					//	Run the analyzer
					criterionResult = testAnalyzer->Run((DOMElement*)testNode);
					
					//	Write the criterion to the results file
					this->resultWriter->AddCriterion(resultDefinition, "configuration", negateAttr, test_refAttr, ResultToString(criterionResult), commentAttr);

					// Use the negate attribute
					if(negateAttr.compare("true") == 0) 
						criterionResult = NegateResult(criterionResult);

					//	Store the result
					configurationResults.push_back(criterionResult);
					
				} catch(Exception ex) {

					// Should this be a fatal error that stops that analyzer?

					//	Add the result attribute to the test that failed
					criterionResult = unknown_result;

					//	Store the result
					configurationResults.push_back(criterionResult);

					//	Write the criterion to the results file
					this->resultWriter->AddCriterion(resultDefinition, "configuration", negateAttr, test_refAttr, ResultToString(criterionResult), commentAttr);


					//	Display a message if verbose mode
					if(Log::verboseMode) {

						string msg = "     Error: An unexpected error occured while evaluating oval definition ";
						msg.append(definitionId);
						if(test_refAttr.compare("") != 0) {

							msg.append(" test_ref: ");
							msg.append(test_refAttr);
						}
						msg.append(".\n     Message: " + ex.GetErrorMessage());

						Log::WriteLog(msg);
						cout << msg << endl;
					}
				}
			}
		}

		// Combine configuration results
		configurationResult = CombineResultsByOperation(&configurationResults, configurationOperation);

		// Set the configuration result
		this->resultWriter->SetCriterionParentResult(resultDefinition, "configuration", ResultToString(configurationResult));

		// Add the configuration result to the criteria results vector
		criteriaResults.push_back(configurationResult);
	}

	///////////////////////////////////////////////////////////////////////////
	//	Create final result - based on useConfiguration flag
	///////////////////////////////////////////////////////////////////////////
	Result criteriaResult;
	Result combinedResult = CombineResultsByOperation(&criteriaResults, "AND");		
	if(useConfiguration) {
		criteriaResult = combinedResult;
	} else {
		criteriaResult = softwareResult;
	}
	
	// Set the optional results attribute tot he combined value
	DOMElement* criteriaElm = XmlCommon::FindNode(resultDefinition, "criteria");
	this->resultWriter->SetResult(criteriaElm, ResultToString(combinedResult));

	return criteriaResult;
}

