/*====================================================================*
 *
 *   Copyright (c) 2013 Qualcomm Atheros, Inc.
 *
 *   All rights reserved.
 *
 *   Redistribution and use in source and binary forms, with or 
 *   without modification, are permitted (subject to the limitations 
 *   in the disclaimer below) 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 Qualcomm Atheros nor the names of 
 *     its contributors may be used to endorse or promote products 
 *     derived from this software without specific prior written 
 *     permission.
 *
 *   NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE 
 *   GRANTED BY THIS LICENSE. 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.  
 *
 *--------------------------------------------------------------------*/

/*====================================================================*
 *
 *   pev.c - QCA Plug-in Electric Vehicle Emulator;
 *
 *   This program, in the current state, is not a finished product;
 *   It has been released so that interested parties can begin to 
 *   see how the SLAC protocol might be implemented;
 *
 *   Some key design features are:
 *
 *   1) the use of a channel variable to abstract ISO Layer 2 I/O;
 *      the variable is used by functions openchannel, readmessage,
 *      sendmessage and closechannel;
 *
 *   2) the use of a message variable to represent an IEEE 802.3 
 *      Ethernet frame; the variable allows one frame to be used
 *      and re-used throughout the program but supports multiple
 *      frame buffers if needed;
 *
 *   3) the use of a session variable to support multiple PEV-EVSE
 *      interactions without using threads or subrocesses; this has
 *      not demonstrated in this version of the program; some more
 *      work is needed;
 *
 *   4) the absence of threads or subprocesses so that the  program 
 *      can be ported to hosts without a multi-tasking operating 
 *      system;
 *
 *   5) lots of debugging messages; these can be suppressed or 
 *      deleted if not wanted;
 *
 *   6) simplified state machine;
 *
 *--------------------------------------------------------------------*/

/*====================================================================*
 *   system header files;
 *--------------------------------------------------------------------*/

#include <unistd.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <errno.h>
#include <time.h>

/*====================================================================*
 *   custom header files;
 *--------------------------------------------------------------------*/

#include "../tools/getoptv.h"
#include "../tools/putoptv.h"
#include "../tools/memory.h"
#include "../tools/number.h"
#include "../tools/types.h"
#include "../tools/flags.h"
#include "../tools/files.h"
#include "../tools/error.h"
#include "../tools/config.h"
#include "../ether/channel.h"
#include "../iso15118/slac.h"

/*====================================================================*
 *   custom source files;
 *--------------------------------------------------------------------*/

#ifndef MAKEFILE
#include "../tools/getoptv.c"
#include "../tools/putoptv.c"
#include "../tools/version.c"
#include "../tools/hexdump.c"
#include "../tools/hexdecode.c"
#include "../tools/hexencode.c"
#include "../tools/hexstring.c"
#include "../tools/decdecode.c"
#include "../tools/decstring.c"
#include "../tools/uintspec.c"
#include "../tools/todigit.c"
#include "../tools/strfbits.c"
#include "../tools/config.c"
#include "../tools/memincr.c"
#include "../tools/error.c"
#endif

#ifndef MAKEFILE
#include "../plc/Devices.c"
#endif

#ifndef MAKEFILE
#include "../mme/EthernetHeader.c"
#include "../mme/QualcommHeader.c"
#include "../mme/HomePlugHeader1.c"
#include "../mme/UnwantedMessage.c"
#include "../mme/readmessage.c"
#include "../mme/sendmessage.c"
#endif

#ifndef MAKEFILE
#include "../ether/channel.c"
#include "../ether/openchannel.c"
#include "../ether/closechannel.c"
#include "../ether/sendpacket.c"
#include "../ether/readpacket.c"
#endif

#ifndef MAKEFILE
#include "../iso15118/slac_session.c"
#include "../iso15118/slac_connect.c"
#include "../iso15118/slac_debug.c"
#include "../iso15118/pev_cm_slac_param.c"
#include "../iso15118/pev_cm_start_atten_char.c"
#include "../iso15118/pev_cm_atten_char.c"
#include "../iso15118/pev_cm_mnbc_sound.c"
#include "../iso15118/pev_cm_slac_match.c"
#include "../iso15118/pev_cm_set_key.c"
#endif

/*====================================================================*
 *   program constants;
 *--------------------------------------------------------------------*/

#define PLCDEVICE "PLC"
#define PROFILE "pev.ini"
#define SECTION "default"   

#define PEV_STATE_DISCONNECTED 1
#define PEV_STATE_UNMATCHED 2
#define PEV_STATE_MATCHED 3

#define PEV_VID "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" // VehicleIdentifier
#define PEV_NMK "50D3E4933F855B7040784DF815AA8DB7"   // HomePlugAV
#define PEV_NID "B0F2E695666B03"		     // HomePlugAV

/*====================================================================*
 *   program variables;
 *--------------------------------------------------------------------*/

unsigned state = 0; 

/*====================================================================*
 *
 *   static void configure ();
 *
 *   print template PEV-HLE configuration file on stdout so that 
 *   profile, section and element names match;
 *
 *--------------------------------------------------------------------*/

static void configure () 

{ 
	printf ("# file: %s\n", PROFILE); 
	printf ("# ====================================================================\n"); 
	printf ("# PEV-HLE initialization;\n"); 
	printf ("# --------------------------------------------------------------------\n"); 
	printf ("[%s]\n", SECTION); 
	printf ("vehicle identifier = %s\n", PEV_VID); 
	printf ("network membership key = %s\n", PEV_NMK); 
	printf ("network identifier = %s\n", PEV_NID); 
	printf ("attenuation threshold = %d\n", SLAC_LIMIT); 
	printf ("msound pause = %d\n", SLAC_PAUSE); 
	printf ("charge time = %d\n", SLAC_CHARGETIME); 
	printf ("settle time = %d\n", SLAC_SETTLETIME); 
	return; 
} 

/*====================================================================*
 *
 *   void initialize (struct session * session, char const * profile, char const * section);
 *
 *   read PEV-HLE configuration profile; initialize session variable;
 *
 *--------------------------------------------------------------------*/

static void initialize (struct session * session, char const * profile, char const * section) 

{ 
	session->next = session->prev = session; 
	hexencode (session->PEV_ID, sizeof (session->PEV_ID), configstring (profile, section, "VehicleIdentifier", PEV_VID)); 
	hexencode (session->NMK, sizeof (session->NMK), configstring (profile, section, "NetworkMembershipKey", PEV_NMK)); 
	hexencode (session->NID, sizeof (session->NID), configstring (profile, section, "NetworkIdentifier", PEV_NID)); 
	session->limit = confignumber_range (profile, section, "AttenuationThreshold", SLAC_LIMIT, 0, UINT_MAX); 
	session->pause = confignumber_range (profile, section, "MSoundPause", SLAC_PAUSE, 0, UINT_MAX); 
	session->settletime = confignumber_range (profile, section, "SettleTime", SLAC_SETTLETIME, 0, UINT_MAX); 
	session->chargetime = confignumber_range (profile, section, "ChargeTime", SLAC_CHARGETIME, 0, UINT_MAX); 
	session->state = PEV_STATE_DISCONNECTED; 
	memcpy (session->original_nmk, session->NMK, sizeof (session->original_nmk)); 
	memcpy (session->original_nid, session->NID, sizeof (session->original_nid)); 
	slac_session (session); 
	return; 
} 

/*====================================================================*
 *
 *   signed identifier (struct session * session, struct channel * channel);
 *
 *   generate the run identifier and store in session variable;
 *
 *   copy channel host address to session PEV MAC address; set session
 *   PEV identifier to zeros;
 *
 *--------------------------------------------------------------------*/

static signed identifier (struct session * session, struct channel * channel) 

{ 
	time_t now; 
	time (& now); 
	memset (session->RunID, 0, sizeof (session->RunID)); 
	memcpy (session->RunID, channel->host, ETHER_ADDR_LEN); 
	memcpy (session->PEV_MAC, channel->host, sizeof (session->PEV_MAC)); 
	return (0); 
} 

/*====================================================================*
 *
 *   void DisconnectedState (struct session * session, struct channel * channel, struct message * message);
 *
 *--------------------------------------------------------------------*/

static void DisconnectedState (struct session * session, struct channel * channel, struct message * message) 

{ 
	slac_session (session); 
	slac_debug (session, 0, __func__, "Probing ..."); 
	memincr (session->RunID, sizeof (session->RunID)); 
	while (pev_cm_slac_param (session, channel, message)); 
	session->state = PEV_STATE_UNMATCHED; 
	return; 
} 

/*====================================================================*
 *
 *   void MatchingState (struct session * session, struct channel * channel, struct message * message);
 *
 *   The PEV-EVSE perform GreenPPEA protocol in this state;
 *
 *   the cm_start_atten_char and cm_mnbc_sound messages are sent
 *   broadcast; the application may receive multiple cm_atten_char
 *   messages before sending the cm_slac_match message;
 *
 *--------------------------------------------------------------------*/

static void UnmatchedState (struct session * session, struct channel * channel, struct message * message) 

{ 
	slac_session (session); 
	slac_debug (session, 0, __func__, "Sounding ..."); 
	if (pev_cm_start_atten_char (session, channel, message)) 
	{ 
		session->state = PEV_STATE_DISCONNECTED; 
		return; 
	} 
	if (pev_cm_mnbc_sound (session, channel, message)) 
	{ 
		session->state = PEV_STATE_DISCONNECTED; 
		return; 
	} 
	if (pev_cm_atten_char (session, channel, message)) 
	{ 
		session->state = PEV_STATE_DISCONNECTED; 
		return; 
	} 
	if (slac_connect (session)) 
	{ 
		session->state = PEV_STATE_DISCONNECTED; 
		return; 
	} 
	slac_debug (session, 0, __func__, "Matching ..."); 
	if (pev_cm_slac_match (session, channel, message)) 
	{ 
		session->state = PEV_STATE_DISCONNECTED; 
		return; 
	} 
	session->state = PEV_STATE_MATCHED; 
	return; 
} 

/*====================================================================*
 *
 *   void MatchedState (struct session * session, struct channel * channel, struct message * message);
 *
 *   charge vehicle; restore original NMK/NID and disconnect; loop
 *   if SLAC_CONTINUE is set;
 *
 *--------------------------------------------------------------------*/

static void MatchedState (struct session * session, struct channel * channel, struct message * message) 

{ 
	slac_session (session); 
	slac_debug (session, 0, __func__, "Connecting ..."); 

#if SLAC_AVLN_EVSE

	slac_debug (session, 0, __func__, "waiting for evse to settle ..."); 
	sleep (session->settletime); 

#endif
#if SLAC_AVLN_PEV

	if (pev_cm_set_key (session, channel, message)) 
	{ 
		session->state = PEV_STATE_DISCONNECTED; 
		return; 
	} 
	sleep (session->settletime); 

#endif

	slac_debug (session, 0, __func__, "Charging (%d) ...\n\n", session->counter++); 
	sleep (session->chargetime); 
	slac_debug (session, 0, __func__, "Disconnecting ..."); 

#if SLAC_AVLN_EVSE

	slac_debug (session, 0, __func__, "waiting for evse to settle ..."); 
	sleep (session->settletime); 

#endif

#if SLAC_AVLN_PEV

	memcpy (session->NMK, session->original_nmk, sizeof (session->NMK)); 
	memcpy (session->NID, session->original_nid, sizeof (session->NID)); 
	if (pev_cm_set_key (session, channel, message)) 
	{ 
		session->state = PEV_STATE_DISCONNECTED; 
		return; 
	} 
	sleep (session->settletime); 

#endif

	session->state = state; 
	return; 
} 

/*====================================================================*
 *   
 *   int main (int argc, char * argv[]);
 *   
 *
 *--------------------------------------------------------------------*/

int main (int argc, char const * argv []) 

{ 
	extern struct channel channel; 
	static char const * optv [] = 
	{ 
		"cCdi:lp:qs:t:vx", 
		"", 
		"Plug-in Electric Vehicle Emulator", 
		"c\tprint template configuration file on stdout", 
		"C\tstop on count mismatch", 
		"d\tdisplay debug information", 

#if defined (WINPCAP) || defined (LIBPCAP)

		"i n\thost interface is (n) [" LITERAL (CHANNEL_ETHNUMBER) "]", 

#else

		"i s\thost interface is (s) [" LITERAL (CHANNEL_ETHDEVICE) "]", 

#endif

		"l\tloop indefinitely", 
		"p s\tconfiguration profile is (s) [" LITERAL (PROFILE) "]", 
		"q\tsuppress normal output", 
		"s s\tconfiguration section is (s) [" LITERAL (SECTION) "]", 
		"t n\tread timeout is (n) milliseconds [" LITERAL (SLAC_TIMEOUT) "]", 
		"v\tverbose messages on stdout", 
		"x\texit on error", 
		(char const *) (0)
	}; 
	struct session session; 
	struct message message; 
	char const * profile = PROFILE; 
	char const * section = SECTION; 
	signed c; 
	memset (& session, 0, sizeof (session)); 
	memset (& message, 0, sizeof (message)); 
	channel.timeout = SLAC_TIMEOUT; 
	if (getenv (PLCDEVICE)) 
	{ 

#if defined (WINPCAP) || defined (LIBPCAP)

		channel.ifindex = atoi (getenv (PLCDEVICE)); 

#else

		channel.ifname = strdup (getenv (PLCDEVICE)); 

#endif

	} 
	optind = 1; 
	while (~ (c = getoptv (argc, argv, optv))) 
	{ 
		switch (c) 
		{ 
		case 'c': 
			configure (); 
			return (0); 
		case 'C': 
			_setbits (session.flags, SLAC_COMPARE); 
			break; 
		case 'd': 
			_setbits (session.flags, (SLAC_VERBOSE | SLAC_SESSION)); 
			break; 
		case 'i': 

#if defined (WINPCAP) || defined (LIBPCAP)

			channel.ifindex = atoi (optarg); 

#else

			channel.ifname = optarg; 

#endif

			break; 
		case 'l': 
			state = PEV_STATE_DISCONNECTED; 
			break; 
		case 'p': 
			profile = optarg; 
			break; 
		case 's': 
			section = optarg; 
			break; 
		case 'q': 
			_setbits (channel.flags, CHANNEL_SILENCE);
			_setbits (session.flags, SLAC_SILENCE);
			break; 
		case 't': 
			channel.timeout = (unsigned) (uintspec (optarg, 0, UINT_MAX)); 
			break; 
		case 'v': 
			_setbits (channel.flags, CHANNEL_VERBOSE); 
			break; 
		case 'x': 
			session.exit = session.exit? 0: 1; 
			break; 
		default: 
			break; 
		} 
	} 
	argc -= optind; 
	argv += optind; 
	if (argc) 
	{ 
		slac_debug (& session, 1, __func__, ERROR_TOOMANY); 
	} 
	openchannel (& channel); 
	identifier (& session, & channel); 
	initialize (& session, profile, section); 
	if (pev_cm_set_key (& session, & channel, & message)) 
	{ 
		slac_debug (& session, 1, __func__, "Can't set key"); 
	} 
	sleep (session.settletime); 
	while (session.state) 
	{ 
		if (session.state == PEV_STATE_DISCONNECTED) 
		{ 
			DisconnectedState (& session, & channel, & message); 
			continue; 
		} 
		if (session.state == PEV_STATE_UNMATCHED) 
		{ 
			UnmatchedState (& session, & channel, & message); 
			continue; 
		} 
		if (session.state == PEV_STATE_MATCHED) 
		{ 
			MatchedState (& session, & channel, & message); 
			continue; 
		} 
		slac_debug (& session, 1, __func__, "Illegal state!"); 
	} 
	closechannel (& channel); 
	return (0); 
}