/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define __STDC_CONSTANT_MACROS
#include <log4cxx/logstring.h>
#include <log4cxx/helpers/simpledateformat.h>
#include "../logunit.h"
#include <log4cxx/helpers/pool.h>
#include "../insertwide.h"
#include <apr.h>
#include <apr_time.h>

using namespace log4cxx;
using namespace log4cxx::helpers;

#define LOG4CXX_TEST 1
#include <log4cxx/private/log4cxx_private.h>

#if LOG4CXX_HAS_STD_LOCALE
	#include <locale>
	#define MAKE_LOCALE(ptr, id)     \
		std::locale loco(id);        \
		std::locale* ptr = &loco;
#else
	#define MAKE_LOCALE(ptr, id)     \
		std::locale* ptr = NULL;
#endif

/**
 * Unit test for {@link SimpleDateFormat}.
 */
LOGUNIT_CLASS(SimpleDateFormatTestCase)
{
	LOGUNIT_TEST_SUITE( SimpleDateFormatTestCase );
	LOGUNIT_TEST( testNameFields );
	LOGUNIT_TEST( testExtremeTimesDoNotReadOutOfBounds );
	LOGUNIT_TEST_SUITE_END();

#define MICROSECONDS_PER_DAY APR_INT64_C(86400000000)

public:
	/**
	 * The day-name, month-name and AM/PM tokens index fixed-size name tables.
	 * Verify that ordinary formatting of those fields is unchanged after the
	 * bounds-checking added to guard against out-of-range time fields.
	 */
	void testNameFields()
	{
		// 02 Jan 2004 00:00:00 GMT is a Friday.
		apr_time_t jan2_2004 = MICROSECONDS_PER_DAY * 12419;
		MAKE_LOCALE(localeUS, "C");

		{
			SimpleDateFormat fmt(LOG4CXX_STR("EEE"), localeUS);
			fmt.setTimeZone(TimeZone::getGMT());
			LogString actual;
			fmt.format(actual, jan2_2004);
			LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("Fri")), actual);
		}
		{
			SimpleDateFormat fmt(LOG4CXX_STR("MMM"), localeUS);
			fmt.setTimeZone(TimeZone::getGMT());
			LogString actual;
			fmt.format(actual, jan2_2004);
			LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("Jan")), actual);
		}
		{
			SimpleDateFormat fmt(LOG4CXX_STR("a"), localeUS);
			fmt.setTimeZone(TimeZone::getGMT());
			LogString actual;
			fmt.format(actual, jan2_2004);
			LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("AM")), actual);
		}
	}

	/**
	 * Extreme apr_time_t values can make APR's time-explosion arithmetic
	 * overflow and produce an out-of-range tm_wday / tm_mon / tm_hour. The
	 * day-name, month-name and AM/PM tokens previously indexed their fixed-size
	 * name tables with those unchecked values, which is an out-of-bounds
	 * std::vector read (a heap-buffer-overflow flagged by AddressSanitizer).
	 *
	 * This exercises the affected tokens with such values and asserts the format
	 * completes with a bounded result rather than reading out of bounds.
	 */
	void testExtremeTimesDoNotReadOutOfBounds()
	{
		MAKE_LOCALE(localeUS, "C");

		const apr_time_t times[] =
		{ (apr_time_t) APR_INT64_MAX
		, (apr_time_t) APR_INT64_MIN
		, (apr_time_t)(APR_INT64_MAX - 1)
		, (apr_time_t) APR_INT64_C(-19503744426494601)
		, (apr_time_t) APR_INT64_C(9000000000000000000)
		, (apr_time_t) APR_INT64_C(-9000000000000000000)
		};

		const logchar* patterns[] =
		{ LOG4CXX_STR("EEE")
		, LOG4CXX_STR("EEEE")
		, LOG4CXX_STR("MMM")
		, LOG4CXX_STR("MMMM")
		, LOG4CXX_STR("a")
		, LOG4CXX_STR("EEEE, d MMMM yyyy a")
		};

		for (auto t : times)
		{
			for (auto p : patterns)
			{
				SimpleDateFormat fmt(LogString(p), localeUS);
				LogString actual;
				fmt.format(actual, t);          // must not read out of bounds
				LOGUNIT_ASSERT(actual.length() < 1000);
			}
		}
	}
};

LOGUNIT_TEST_SUITE_REGISTRATION(SimpleDateFormatTestCase);
