/*====================================================================*
 *
 *   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.
 *
 *--------------------------------------------------------------------*/

/*====================================================================*
 *
 *   getpib.c - PIB Data Extractor
 *
 *   Contributor(s):
 *      Charles Maier
 *
 *--------------------------------------------------------------------*/

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

#include <unistd.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <ctype.h>
#include <inttypes.h>

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

#include "../tools/getoptv.h"
#include "../tools/memory.h"
#include "../tools/number.h"
#include "../tools/error.h"
#include "../tools/types.h"
#include "../tools/flags.h"
#include "../tools/files.h"
#include "../pib/pib.h"
#include "../nvm/nvm.h"

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

#ifndef MAKEFILE
#include "../tools/getoptv.c"
#include "../tools/putoptv.c"
#include "../tools/version.c"
#include "../tools/uintspec.c"
#include "../tools/basespec.c"
#include "../tools/todigit.c"
#include "../tools/hexout.c"
#include "../tools/error.c"
#include "../tools/checksum32.c"
#include "../tools/fdchecksum32.c"
#endif

#ifndef MAKEFILE
#include "../nvm/nvmseek2.c"
#endif

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

#define GETPIB_COMMA ' '
#define GETPIB_TOOBIG "object '%s' exceeds extent of " SIZE_T_SPEC " bytes"
#define GETPIB_NOSIZE "object '%s' has no length"

#define GETPIB_VERBOSE (1 << 0)
#define GETPIB_SILENCE (1 << 1)
#define GETPIB_NEWLINE (1 << 2)

/*====================================================================*
 *
 *   void getmemory (byte const * memory, size_t extent, char const * object, size_t length);
 *
 *--------------------------------------------------------------------*/

static void getmemory (byte const * memory, size_t extent, char const * object, size_t length)

{
	if (length > extent)
	{
		error (1, ECANCELED, GETPIB_TOOBIG, object, length);
	}
	hexout (memory, length, ':', '\0', stdout);
	return;
}

/*====================================================================*
 *
 *   void getstring (byte const * memory, size_t extent, char const * object, size_t length);
 *
 *--------------------------------------------------------------------*/

static void getstring (byte const * memory, size_t extent, char const * object, size_t length)

{
	char const * string = (char const *) (memory);
	if (length > extent)
	{
		error (1, ECANCELED, GETPIB_TOOBIG, object, length);
	}
	while (isprint (* string) && (length--))
	{
		putc (* string++, stdout);
	}
	return;
}

/*====================================================================*
 *
 *   void snatch (int argc, char const * argv [], byte const * memory, size_t extent, char comma);
 *
 *   extract and print the specified data objects from memory; comma
 *   delimits consecutive objects on output;
 *
 *--------------------------------------------------------------------*/

static void snatch (int argc, char const * argv [], byte const * memory, size_t extent, char comma)

{
	size_t length = 0;
	size_t offset = 0;
	if (! (argc) || ! (* argv))
	{
		error (1, ECANCELED, "Need an offset");
	}
	offset = (size_t) (basespec (* argv, 16, sizeof (uint32_t)));
	if (offset > extent)
	{
		error (1, ECANCELED, "offset " SIZE_T_SPEC " exceeds extent of " SIZE_T_SPEC " bytes", offset, extent);
	}
	memory += offset;
	extent -= offset;
	argc--;
	argv++;
	while ((argc) && (* argv))
	{
		char const * object = * argv;
		argc--;
		argv++;
		if (! strcmp (object, "byte"))
		{
			uint8_t * number = (uint8_t *) (memory);
			if (sizeof (* number) > extent)
			{
				error (1, ECANCELED, GETPIB_TOOBIG, object, extent);
			}
			printf ("%" PRIu8, * number);
			memory += sizeof (* number);
			extent -= sizeof (* number);
		}
		else if (! strcmp (object, "word"))
		{
			uint16_t * number = (uint16_t *) (memory);
			if (sizeof (* number) > extent)
			{
				error (1, ECANCELED, GETPIB_TOOBIG, object, extent);
			}
			printf ("%" PRIu16, LE16TOH (* number));
			memory += sizeof (* number);
			extent -= sizeof (* number);
		}
		else if (! strcmp (object, "long"))
		{
			uint32_t * number = (uint32_t *) (memory);
			if (sizeof (* number) > extent)
			{
				error (1, ECANCELED, GETPIB_TOOBIG, object, extent);
			}
			printf ("%" PRIu32, LE32TOH (* number));
			memory += sizeof (* number);
			extent -= sizeof (* number);
		}
		else if (! strcmp (object, "huge"))
		{
			uint64_t * number = (uint64_t *) (memory);
			if (sizeof (* number) > extent)
			{
				error (1, ECANCELED, GETPIB_TOOBIG, object, extent);
			}
			printf ("%" PRIu64, LE64TOH (* number));
			memory += sizeof (* number);
			extent -= sizeof (* number);
		}

#if 1

		else if (! strcmp (object, "xbyte"))
		{
			uint8_t * number = (uint8_t *) (memory);
			if (sizeof (* number) > extent)
			{
				error (1, ECANCELED, GETPIB_TOOBIG, object, extent);
			}
			printf ("0x%02" PRIX8, * number);
			memory += sizeof (* number);
			extent -= sizeof (* number);
		}
		else if (! strcmp (object, "xword"))
		{
			uint16_t * number = (uint16_t *) (memory);
			if (sizeof (* number) > extent)
			{
				error (1, ECANCELED, GETPIB_TOOBIG, object, extent);
			}
			printf ("0x%04" PRIX16, LE16TOH (* number));
			memory += sizeof (* number);
			extent -= sizeof (* number);
		}
		else if (! strcmp (object, "xlong"))
		{
			uint32_t * number = (uint32_t *) (memory);
			if (sizeof (* number) > extent)
			{
				error (1, ECANCELED, GETPIB_TOOBIG, object, extent);
			}
			printf ("0x%08" PRIX32, LE32TOH (* number));
			memory += sizeof (* number);
			extent -= sizeof (* number);
		}
		else if (! strcmp (object, "xhuge"))
		{
			uint64_t * number = (uint64_t *) (memory);
			if (sizeof (* number) > extent)
			{
				error (1, ECANCELED, GETPIB_TOOBIG, object, extent);
			}
			printf ("0x%016" PRIX64, LE64TOH (* number));
			memory += sizeof (* number);
			extent -= sizeof (* number);
		}

#endif

		else if (! strcmp (object, "mac"))
		{
			length = ETHER_ADDR_LEN;
			if (length > extent)
			{
				error (1, ECANCELED, GETPIB_TOOBIG, object, extent);
			}
			getmemory (memory, extent, object, length);
			memory += length;
			extent -= length;
		}
		else if (! strcmp (object, "key"))
		{
			length = PIB_KEY_LEN;
			if (length > extent)
			{
				error (1, ECANCELED, GETPIB_TOOBIG, object, extent);
			}
			getmemory (memory, extent, object, length);
			memory += length;
			extent -= length;
		}
		else if (! strcmp (object, "hfid"))
		{
			length = PIB_HFID_LEN;
			if (length > extent)
			{
				error (1, ECANCELED, GETPIB_TOOBIG, object, extent);
			}
			getstring (memory, extent, object, length);
			memory += length;
			extent -= length;
		}

#if 1

		else if (! strcmp (object, "adminusername") || ! strcmp (object, "adminpassword") || ! strcmp (object, "accessusername"))
		{
			length = PIB_NAME_LEN +  1;
			if (length > extent)
			{
				error (1, ECANCELED, GETPIB_TOOBIG, object, extent);
			}
			getstring (memory, extent, object, length);
			memory += length;
			extent -= length;
		}
		else if (! strcmp (object, "accesspassword"))
		{
			length = PIB_HFID_LEN +  1;
			if (length > extent)
			{
				error (1, ECANCELED, GETPIB_TOOBIG, object, extent);
			}
			getstring (memory, extent, object, length);
			memory += length;
			extent -= length;
		}
		else if (! strcmp (object, "username") || ! strcmp (object, "password") || ! strcmp (object, "url"))
		{
			length = PIB_TEXT_LEN +  1;
			getstring (memory, extent, object, length);
			memory += length;
			extent -= length;
		}

#endif

		else if (! strcmp (object, "data"))
		{
			if (! * argv)
			{
				error (1, EINVAL, GETPIB_NOSIZE, object);
			}
			length = (unsigned) (uintspec (* argv, 1, extent));
			hexout (memory, length, 0, 0, stdout);
			memory += length;
			extent -= length;
			argc--;
			argv++;
		}
		else if (! strcmp (object, "text"))
		{
			if (! * argv)
			{
				error (1, EINVAL, GETPIB_NOSIZE, object);
			}
			length = (unsigned) (uintspec (* argv, 1, extent));
			getstring (memory, extent, object, length);
			memory += length;
			extent -= length;
			argc--;
			argv++;
		}
		else if (! strcmp (object, "skip"))
		{
			if (! * argv)
			{
				error (1, EINVAL, GETPIB_NOSIZE, object);
			}
			length = (unsigned) (uintspec (* argv, 1, extent));
			memory += length;
			extent -= length;
			argc--;
			argv++;
			continue;
		}
		else
		{
			error (1, ENOTSUP, "%s", object);
		}
		if ((argc) && (* argv))
		{
			putc (comma, stdout);
		}
	}
	return;
}

/*====================================================================*
 *
 *   signed pibimage1 (int argc, char const * argv [], char comma);
 *
 *   read an entire flat parameter file into memory, edit it, save
 *   it and display it;
 *
 *   Contributor(s):
 *	Charles Maier
 *
 *--------------------------------------------------------------------*/

static signed pibimage1 (int argc, char const * argv [], char comma)

{
	signed fd;
	off_t extent;
	byte * memory;
	if ((fd = open (* argv, O_BINARY | O_RDWR)) == - 1)
	{
		error (1, errno, FILE_CANTOPEN, * argv);
	}
	if ((extent = lseek (fd, 0, SEEK_END)) == - 1)
	{
		error (1, errno, FILE_CANTSIZE, * argv);
	}
	if (lseek (fd, 0, SEEK_SET))
	{
		error (1, errno, FILE_CANTHOME, * argv);
	}
	if (! (memory = malloc (extent)))
	{
		error (1, errno, FILE_CANTLOAD, * argv);
	}
	if (read (fd, memory, extent) != extent)
	{
		error (1, errno, FILE_CANTREAD, * argv);
	}
	close (fd);
	snatch (argc - 1, argv +  1, memory, extent, comma);
	free (memory);
	return (0);
}

/*====================================================================*
 *
 *   signed pibimage2 (int argc, char const * argv [], char comma);
 *
 *   read an entire flat parameter file into memory, edit it, save
 *   it and display it;
 *
 *   Contributor(s):
 *	Charles Maier
 *
 *--------------------------------------------------------------------*/

static signed pibimage2 (int argc, char const * argv [], char comma)

{
	struct nvm_header2 header;
	signed fd;
	off_t extent;
	byte * memory;
	if ((fd = open (* argv, O_BINARY | O_RDWR)) == - 1)
	{
		error (1, errno, FILE_CANTOPEN, * argv);
	}
	if (nvmseek2 (fd, * argv, & header, NVM_IMAGE_PIB))
	{
		error (1, errno, "Can't find PIB image in %s", * argv);
	}
	extent = LE32TOH (header.ImageLength);
	if (! (memory = malloc (extent)))
	{
		error (1, errno, FILE_CANTLOAD, * argv);
	}
	if (read (fd, memory, extent) != extent)
	{
		error (1, errno, FILE_CANTREAD, * argv);
	}
	close (fd);
	snatch (argc - 1, argv +  1, memory, extent, comma);
	free (memory);
	return (0);
}

/*====================================================================*
 *
 *   signed function (int argc, char const * argv [], char comma);
 *
 *   call an appropriate parameter edit function based on the file
 *   header;
 *
 *   older parameter files are flat with their own header; newer ones
 *   are image chains where one of image contains the parameter block;
 *
 *
 *   Contributor(s):
 *	Charles Maier
 *
 *--------------------------------------------------------------------*/

static signed function (int argc, char const * argv [], char comma)

{
	uint32_t version;
	signed status;
	signed fd;
	if ((fd = open (* argv, O_BINARY | O_RDWR)) == - 1)
	{
		error (1, errno, FILE_CANTOPEN, * argv);
	}
	if (read (fd, & version, sizeof (version)) != sizeof (version))
	{
		error (1, errno, FILE_CANTREAD, * argv);
	}
	close (fd);
	if (LE32TOH (version) == 0x00010001)
	{
		status = pibimage2 (argc, argv, comma);
	}
	else
	{
		status = pibimage1 (argc, argv, comma);
	}
	return (status);
}

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

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

{
	static char const * optv [] =
	{
		"c:qvn",
		"file offset type [size]\n\n\tstandard-length types are 'byte'|'word'|'long'|'huge'|'hfid'|'mac'|'key'\n\tvariable-length types are 'data'|'text'|'skip' and need a size",
		"PIB Data Extractor",
		"c c\tobject separator is (c) [" LITERAL (GETPIB_COMMA) "]",
		"n\tappend newline",
		"q\tquiet mode",
		"v\tverbose mode",
		(char const *) (0)
	};
	flag_t flags = (flag_t) (0);
	char comma = GETPIB_COMMA;
	signed c;
	optind = 1;
	opterr = 1;
	while (~ (c = getoptv (argc, argv, optv)))
	{
		switch (c)
		{
		case 'c':
			comma = * optarg;
			break;
		case 'n':
			_setbits (flags, GETPIB_NEWLINE);
			break;
		case 'q':
			_setbits (flags, GETPIB_SILENCE);
			break;
		case 'v':
			_setbits (flags, GETPIB_VERBOSE);
			break;
		default:
			break;
		}
	}
	argc -= optind;
	argv += optind;
	if ((argc) && (* argv))
	{
		function (argc, argv, comma);
		if (_anyset (flags, GETPIB_NEWLINE))
		{
			putc ('\n', stdout);
		}
	}
	return (0);
}
