/*
 * Parser module for reference clock
 *
 * Copyright Frank Kardel <kardel@ntp.org>
 * Copyright the NTPsec project contributors
 * SPDX-License-Identifier: BSD-3-Clause
 *
 */

#include "config.h"
#include "ntp_fp.h"
#include "timespecops.h"
#include "ntp_calendar.h"
#include "ntp_stdlib.h"
#include "ntp_machine.h"
#include "ntp.h"		/* (get Y2KFixes definitions) 	Y2KFixes */

#include "parse.h"

# include <stdio.h>

/*
 * PPS edge info
 */
#define SYNC_ONE	0x01

static unsigned long timepacket (parse_t *);
static unsigned int parse_restart (parse_t *, char);
static parse_pps_fnc_t pps_simple;

bool
parse_timedout(
	       parse_t *parseio,
	       timestamp_t *tstamp,
	       struct timespec *del
	       )
{
	struct timespec delta;

	l_fp delt;

	delt = *tstamp;
	delt -= parseio->parse_lastchar;
	delta = lfp_uintv_to_tspec(delt);
	if (cmp_tspec(delta, *del) > 0) {
		parseprintf(DD_PARSE, ("parse: timedout: TRUE\n"));
		return true;
	} else {
		parseprintf(DD_PARSE, ("parse: timedout: FALSE\n"));
		return false;
	}
}

/*ARGSUSED*/
bool
parse_ioinit(
	parse_t *parseio
	)
{
	parseprintf(DD_PARSE, ("parse_iostart\n"));

	parseio->parse_plen = 0;
	parseio->parse_pdata = (void *)0;

	parseio->parse_data = 0;
	parseio->parse_ldata = 0;
	parseio->parse_dsize = 0;

	parseio->parse_badformat = 0;
	parseio->parse_ioflags   = PARSE_IO_CS7;	/* usual unix default */
	parseio->parse_index     = 0;
	parseio->parse_ldsize    = 0;

	return true;
}

/*ARGSUSED*/
void
parse_ioend(
	parse_t *parseio
	)
{
	parseprintf(DD_PARSE, ("parse_ioend\n"));

	if (parseio->parse_pdata)
	    free(parseio->parse_pdata);

	if (parseio->parse_data)
	    free(parseio->parse_data);
}

static unsigned int
parse_restart(
	      parse_t *parseio,
	      char ch
	      )
{
	unsigned int updated = PARSE_INP_SKIP;

	/*
	 * re-start packet - timeout - overflow - start symbol
	 */

	if (parseio->parse_index) {
		/*
		 * filled buffer - thus not end character found
		 * do processing now
		 */
		parseio->parse_data[parseio->parse_index] = '\0';
		memcpy(parseio->parse_ldata, parseio->parse_data, (unsigned)(parseio->parse_index+1));
		parseio->parse_ldsize = parseio->parse_index;
		updated = PARSE_INP_TIME;
	}

	parseio->parse_index = 1;
	parseio->parse_data[0] = ch;
	parseprintf(DD_PARSE, ("parse: parse_restart: buffer start (updated = %x)\n", updated));
	return updated;
}

unsigned int
parse_addchar(
	      parse_t *parseio,
	      char ch
	      )
{
	/*
	 * add to buffer
	 */
	if (parseio->parse_index < parseio->parse_dsize) {
		/*
		 * collect into buffer
		 */
		parseprintf(DD_PARSE,
                            ("parse: parse_addchar: buffer[%d] = 0x%x\n",
                             parseio->parse_index, (unsigned)ch));
		parseio->parse_data[parseio->parse_index++] = (char)ch;
		return PARSE_INP_SKIP;
	}
	else
		/*
		 * buffer overflow - attempt to make the best of it
		 */
		return parse_restart(parseio, ch);
}

unsigned int
parse_end(
	  parse_t *parseio
	  )
{
	/*
	 * message complete processing
	 */
	parseio->parse_data[parseio->parse_index] = '\0';
	memcpy(parseio->parse_ldata, parseio->parse_data, (unsigned)(parseio->parse_index+1));
	parseio->parse_ldsize = parseio->parse_index;
	parseio->parse_index = 0;
	parseprintf(DD_PARSE, ("parse: parse_end: buffer end\n"));
	return PARSE_INP_TIME;
}

/*ARGSUSED*/
int
parse_ioread(
	parse_t *parseio,
	char ch,
	timestamp_t *tstamp
	)
{
	unsigned int updated = CVT_NONE;
	/*
	 * within STREAMS CSx (x < 8) chars still have the upper bits set
	 * so we normalize the characters by masking unnecessary bits off.
	 *
	 * (ESR, 2015: Probably not necessary since STREAMS support has
	 * been removed, but harmless.)
	 */
	switch (parseio->parse_ioflags & PARSE_IO_CSIZE) {
	    case PARSE_IO_CS5:
		ch &= 0x1F;
		break;

	    case PARSE_IO_CS6:
		ch &= 0x3F;
		break;

	    case PARSE_IO_CS7:
		ch &= 0x7F;
		break;

	    case PARSE_IO_CS8:
		ch &= (char) 0xFFU;
		break;

            default:
                /* huh? */
                break;
	}

	parseprintf(DD_PARSE, ("parse_ioread(0x%lx, char=0x%x, ..., ...)\n",
                    (unsigned long)parseio, (unsigned)(ch & 0xFF)));

	if (!clockformats[parseio->parse_lformat]->convert) {
		parseprintf(DD_PARSE, ("parse_ioread: input dropped.\n"));
		return CVT_NONE;
	}

	if (clockformats[parseio->parse_lformat]->input) {
		unsigned long input_status;

		input_status = clockformats[parseio->parse_lformat]->input(parseio, ch, tstamp);

		if (input_status & PARSE_INP_SYNTH) {
			updated = CVT_OK;
		}

		if (input_status & PARSE_INP_TIME) {	/* time sample is available */
			updated = (unsigned int) timepacket(parseio);
		}

		if (input_status & PARSE_INP_DATA) { /* got additional data */
			updated |= CVT_ADDITIONAL;
		}
	}


	/*
	 * remember last character time
	 */
	parseio->parse_lastchar = *tstamp;

#ifdef DEBUG
	if ((updated & CVT_MASK) != CVT_NONE) {
		parseprintf(DD_PARSE, ("parse_ioread: time sample accumulated (status=0x%x)\n", updated));
	}
#endif

	parseio->parse_dtime.parse_status = updated;

	return (((updated & CVT_MASK) != CVT_NONE) ||
		((updated & CVT_ADDITIONAL) != 0));
}

/*
 * parse_iodone
 *
 * clean up internal status for new round
 */
/*ARGSUSED*/
void
parse_iodone(
	parse_t *parseio
	)
{
	/*
	 * we need to clean up certain flags for the next round
	 */
	parseprintf(DD_PARSE, ("parse_iodone: DONE\n"));
	parseio->parse_dtime.parse_state = 0; /* no problems with ISRs */
}

/*---------- conversion implementation --------------------*/

/*
 * convert a struct clock to UTC since Jan, 1st 1970 0:00 (the UNIX EPOCH)
 */

time_t
parse_to_unixtime(
	clocktime_t   *clock_time,
	unsigned long *cvtrtc
	)
{
#define SETRTC(_X_)	{ if (cvtrtc) *cvtrtc = (_X_); }
	static int days_of_month[] =
	    {
		    0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
	    };
	int i;
	time_t t;

	if (clock_time->utctime)
		return clock_time->utctime;	/* if the conversion routine gets it right away - why not */

	if ( clock_time->year < YEAR_PIVOT ) {			/* Y2KFixes [ */
		clock_time->year += 100;	/* convert 20xx%100 to 20xx-1900 */
	}
	if ( clock_time->year < YEAR_BREAK ) {	/* expand to full four-digits */
		clock_time->year += 1900;
	}

	if (clock_time->year < 1970 ) {				/* Y2KFixes ] */
		SETRTC(CVT_FAIL|CVT_BADDATE);
		return -1;
	}

	/*
	 * sorry, slow section here - but it's not time critical anyway
	 */
	t = julian0(clock_time->year) - julian0(1970);		/* Y2kFixes */
	/* month */
	if (clock_time->month <= 0 || clock_time->month > 12) {
		SETRTC(CVT_FAIL|CVT_BADDATE);
		return -1;		/* bad month */
	}

	if ( clock_time->month >= 3  &&  is_leapyear(clock_time->year) )
		t++;		/* add one more if within leap year */

	for (i = 1; i < clock_time->month; i++) {
		t += days_of_month[i];
	}
	/* day */
	if (clock_time->day < 1 || ((clock_time->month == 2 && days_per_year(clock_time->year) == 366) ?
				    clock_time->day > 29 : clock_time->day > days_of_month[clock_time->month])) {
		SETRTC(CVT_FAIL|CVT_BADDATE);
		return -1;		/* bad day */
	}

	t += clock_time->day - 1;
	/* hour */
	if (clock_time->hour < 0 || clock_time->hour >= 24) {
		SETRTC(CVT_FAIL|CVT_BADTIME);
		return -1;		/* bad hour */
	}

	t = t*24 + clock_time->hour;

	/* min */
	if (clock_time->minute < 0 || clock_time->minute > 59) {
		SETRTC(CVT_FAIL|CVT_BADTIME);
		return -1;		/* bad min */
	}

	t = t*60 + clock_time->minute;
	/* sec */

	if (clock_time->second < 0 || clock_time->second > 60) {	/* allow for LEAPs */
		SETRTC(CVT_FAIL|CVT_BADTIME);
		return -1;		/* bad sec */
	}

	t  = t*60 + clock_time->second;

	t += clock_time->utcoffset;	/* warp to UTC */

	/* done */

	clock_time->utctime = t;		/* documentray only */

	return t;
}

/*--------------- format conversion -----------------------------------*/

int
Stoi(
	const unsigned char *s,
	long *zp,
	int cnt
	)
{
	char unsigned const *b = s;
	int f,z,v;
	char unsigned c;

	f=z=v=0;

	while(*s == ' ') {
	    s++;
	}

	if (*s == '-')
	{
		s++;
		v = 1;
	}
	else
	    if (*s == '+') {
		s++;
	    }

	for(;;) {
		c = *s++;
		if (c == '\0' || c < '0' || c > '9' || (cnt && ((s-b) > cnt)))
		{
			if (f == 0)
			{
				return(-1);
			}
			if (v) {
			    z = -z;
			}
			*zp = z;
			return(0);
		}
		z = (z << 3) + (z << 1) + ( c - '0' );
		f=1;
	}
}

int
Strok(
	const unsigned char *s,
	const unsigned char *m
	)
{
	if (!s || !m) {
	    return 0;
	}

	while(*s && *m)
	{
		if ((*m == ' ') ? 1 : (*s == *m))
		{
			s++;
			m++;
		}
		else
		{
			return 0;
		}
	}
	return !*m;
}

unsigned long
updatetimeinfo(
	       parse_t *parseio,
	       unsigned long   flags
	       )
{
	parseio->parse_lstate          = parseio->parse_dtime.parse_state | flags | PARSEB_TIMECODE;

	parseio->parse_dtime.parse_state = parseio->parse_lstate;

	parseprintf(DD_PARSE, ("updatetimeinfo status=0x%lx, time=%x\n",
			       (unsigned long)parseio->parse_dtime.parse_state,
	                       lfpuint(parseio->parse_dtime.parse_time)));

	return CVT_OK;		/* everything fine and dandy... */
}

/*
 * parse_pps_fnc_t pps_simple
 *
 * handle a pps time stamp
 */
/*ARGSUSED*/
static unsigned long
pps_simple(
	parse_t *parseio,
	int status,
	timestamp_t *ptime
	)
{
	UNUSED_ARG(status);
	parseio->parse_dtime.parse_ptime  = *ptime;
	parseio->parse_dtime.parse_state |= PARSEB_PPS|PARSEB_S_PPS;

	return CVT_NONE;
}

/*
 * parse_pps_fnc_t pps_one
 *
 * handle a pps time stamp in ONE edge
 */
/*ARGSUSED*/
unsigned long
pps_one(
	parse_t *parseio,
	int status,
	timestamp_t *ptime
	)
{
	if (status) {
		return pps_simple(parseio, status, ptime);
	}

	return CVT_NONE;
}

/*
 * timepacket
 *
 * process a data packet
 */
static unsigned long
timepacket(
	parse_t *parseio
	)
{
	unsigned short format;
	time_t t;
	unsigned long cvtrtc;		/* current conversion result */
	clocktime_t clock_time;

	memset((char *)&clock_time, 0, sizeof clock_time);
	format = parseio->parse_lformat;

	if (format == (unsigned short)~0) {
		return CVT_NONE;
	}

	switch ((cvtrtc = clockformats[format]->convert ?
		 clockformats[format]->convert((unsigned char *)parseio->parse_ldata, parseio->parse_ldsize, (struct format *)(clockformats[format]->data), &clock_time, parseio->parse_pdata) :
		 CVT_NONE) & CVT_MASK)
	{
	case CVT_FAIL:
		parseio->parse_badformat++;
		return cvtrtc;

	case CVT_NONE:
		/*
		 * too bad - pretend bad format
		 */
		parseio->parse_badformat++;
		return CVT_NONE;

	case CVT_OK:
		break;

	case CVT_SKIP:
		return CVT_NONE;

	default:
		/* shouldn't happen */
		msyslog(LOG_WARNING, "ERR: parse: INTERNAL error: bad return code of convert routine \"%s\"", clockformats[format]->name);
		return CVT_FAIL|cvtrtc;
	}

	if ((t = parse_to_unixtime(&clock_time, &cvtrtc)) == -1) {
		return CVT_FAIL|cvtrtc;
	}

	/*
	 * time stamp
	 */
	struct timespec ts = {t, clock_time.usecond * 1000};
	parseio->parse_dtime.parse_time = tspec_stamp_to_lfp(ts);

	parseio->parse_dtime.parse_format       = format;

	return updatetimeinfo(parseio, clock_time.flags);
}

/*ARGSUSED*/
bool
parse_timecode(
	parsectl_t *dct,
	parse_t    *parse
	)
{
	dct->parsegettc.parse_state  = parse->parse_lstate;
	dct->parsegettc.parse_format = parse->parse_lformat;
	/*
	 * move out current bad packet count
	 * user program is expected to sum these up
	 * this is not a problem, as "parse" module are
	 * exclusive open only
	 */
	dct->parsegettc.parse_badformat = parse->parse_badformat;
	parse->parse_badformat = 0;

	if (parse->parse_ldsize <= PARSE_TCMAX) {
		dct->parsegettc.parse_count = parse->parse_ldsize;
		memcpy(dct->parsegettc.parse_buffer, parse->parse_ldata, dct->parsegettc.parse_count);
		return true;
	} else {
		return false;
	}
}


/*ARGSUSED*/
bool
parse_setfmt(
	parsectl_t *dct,
	parse_t    *parse
	)
{
	if (dct->parseformat.parse_count <= PARSE_TCMAX) {
		if (dct->parseformat.parse_count) {
			unsigned short i;

			for (i = 0; i < nformats; i++) {
				if (!strcmp(dct->parseformat.parse_buffer, clockformats[i]->name)) {
					if (parse->parse_pdata)
						free(parse->parse_pdata);
					parse->parse_pdata = 0;

					parse->parse_plen = clockformats[i]->plen;

					if (parse->parse_plen) {
						parse->parse_pdata = malloc(parse->parse_plen);
						if (!parse->parse_pdata) {
							parseprintf(DD_PARSE, ("set format failed: malloc for private data area failed\n"));
							return false;
						}
						memset((char *)parse->parse_pdata, 0, parse->parse_plen);
					}

					if (parse->parse_data) {
						free(parse->parse_data);
					}
					parse->parse_ldata = parse->parse_data = 0;

					parse->parse_dsize = clockformats[i]->length;

					if (parse->parse_dsize) {
						parse->parse_data = (char*)malloc((unsigned)(parse->parse_dsize * 2 + 2));
						if (!parse->parse_data) {
							if (parse->parse_pdata)
								free(parse->parse_pdata);
							parse->parse_pdata = 0;

							parseprintf(DD_PARSE, ("init failed: malloc for data area failed\n"));
							return false;
						}
					}


					/*
					 * leave room for '\0'
					 */
					parse->parse_ldata     = parse->parse_data + parse->parse_dsize + 1;

					parse->parse_lformat  = i;

					return true;
				}
			}
		}
	}
	return false;
}

/*ARGSUSED*/
int
parse_getfmt(
	parsectl_t *dct,
	parse_t    *parse
	)
{
	UNUSED_ARG(dct);
	UNUSED_ARG(parse);
	if (dct->parseformat.parse_format < nformats &&
	    strlen(clockformats[dct->parseformat.parse_format]->name) <= PARSE_TCMAX) {
		dct->parseformat.parse_count = (unsigned short) (strlen(clockformats[dct->parseformat.parse_format]->name) + 1);
		memcpy(dct->parseformat.parse_buffer, clockformats[dct->parseformat.parse_format]->name, dct->parseformat.parse_count);
		return 1;
	} else {
		return 0;
	}
}

/*ARGSUSED*/
bool
parse_setcs(
	parsectl_t *dct,
	parse_t    *parse
	)
{
	parse->parse_ioflags &= ~PARSE_IO_CSIZE;
	parse->parse_ioflags |= (int) (dct->parsesetcs.parse_cs & PARSE_IO_CSIZE);
	return true;
}

/*
 * History:
 *
 * parse.c,v
 * Revision 4.20  2005/08/06 17:39:40  kardel
 * cleanup size handling wrt/ to buffer boundaries
 *
 * Revision 4.19  2005/04/16 17:32:10  kardel
 * update copyright
 *
 * Revision 4.18  2004/11/14 16:11:05  kardel
 * update Id tags
 *
 * Revision 4.17  2004/11/14 15:29:41  kardel
 * support PPSAPI, upgrade Copyright to Berkeley style
 *
 * Revision 4.14  1999/11/28 09:13:52  kardel
 * RECON_4_0_98F
 *
 * Revision 4.13  1999/02/28 11:50:20  kardel
 * (timepacket): removed unnecessary code
 *
 * Revision 4.12  1999/02/21 12:17:44  kardel
 * 4.91f reconciliation
 *
 * Revision 4.11  1999/02/21 11:09:47  kardel
 * unified debug output
 *
 * Revision 4.10  1998/12/20 23:45:30  kardel
 * fix types and warnings
 *
 * Revision 4.9  1998/08/09 22:26:06  kardel
 * Trimble TSIP support
 *
 * Revision 4.8  1998/06/14 21:09:39  kardel
 * Sun acc cleanup
 *
 * Revision 4.7  1998/06/13 15:19:13  kardel
 * fix mem*() to b*() function macro emulation
 *
 * Revision 4.6  1998/06/13 13:24:13  kardel
 * printf fmt
 *
 * Revision 4.5  1998/06/13 13:01:10  kardel
 * printf fmt
 *
 * Revision 4.4  1998/06/13 12:12:10  kardel
 * bcopy/memcpy cleanup
 * fix SVSV name clash
 *
 * Revision 4.3  1998/06/12 15:22:30  kardel
 * fix prototypes
 *
 * Revision 4.2  1998/06/12 09:13:27  kardel
 * conditional compile macros fixed
 * printf prototype
 *
 * Revision 4.1  1998/05/24 09:39:55  kardel
 * implementation of the new IO handling model
 *
 * Revision 4.0  1998/04/10 19:45:36  kardel
 * Start 4.0 release version numbering
 *
 * from V3 3.46 log info deleted 1998/04/11 kardel
 */
