Example #1
0
int main (int argc, char const * argv [])

{
	extern struct channel channel;
	extern void terminate (signo_t);
	static char const * optv [] =
	{
		"dFi:n:N:p:P:qS:t:vw:x",
		"-N file -P file [-S file]",
		"Qualcomm Atheros Panther/Lynx PLC Host Daemon",
		"d\texecute in background as daemon",
		"F [F]\tflash [force] non-volatile memory",

#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

		"n f\tread firmware from device into file (f)",
		"N f\tfirmware file is (f)",
		"p f\tread parameters from device into file (f)",
		"P f\tparameter file is (f)",
		"q\tquiet mode",
		"S f\tsoftloader file is (f)",
		"t n\tread timeout is (n) milliseconds [" LITERAL (CHANNEL_TIMEOUT) "]",
		"v\tverbose mode",
		"w n\twakeup every (n) seconds [" LITERAL (PLC_LONGTIME) "]",
		"x\texit on error",
		(char const *) (0)
	};

#include "../plc/plc.c"

	struct sigaction sa;
	char const * socketname = SOCKETNAME;
	signed c;
	if (getenv (PLCDEVICE))
	{

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

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

#else

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

#endif

	}
	optind = 1;
	plc.timer = PLC_LONGTIME;
	while ((c = getoptv (argc, argv, optv)) != -1)
	{
		switch (c)
		{
		case 'd':
			_setbits (plc.flags, PLC_DAEMON);
			break;
		case 'F':
			_setbits (plc.module, (VS_MODULE_MAC | VS_MODULE_PIB));
			if (_anyset (plc.flags, PLC_FLASH_DEVICE))
			{
				_setbits (plc.module, VS_MODULE_FORCE);
			}
			_setbits (plc.flags, PLC_FLASH_DEVICE);
			break;
		case 'i':

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

			channel.ifindex = atoi (optarg);

#else

			channel.ifname = optarg;

#endif

			break;
		case 'N':
			if (!checkfilename (optarg))
			{
				error (1, EINVAL, "%s", optarg);
			}
			if ((plc.NVM.file = open (plc.NVM.name = optarg, O_BINARY|O_RDONLY)) == -1)
			{
				error (1, errno, "%s", plc.NVM.name);
			}
			if (nvmfile2 (&plc.NVM))
			{
				error (1, errno, "Bad firmware file: %s", plc.NVM.name);
			}
			_setbits (plc.flags, PLC_WRITE_MAC);
			break;
		case 'n':
			if (!checkfilename (optarg))
			{
				error (1, EINVAL, "%s", optarg);
			}
			if ((plc.nvm.file = open (plc.nvm.name = optarg, O_BINARY|O_CREAT|O_RDWR|O_TRUNC, FILE_FILEMODE)) == -1)
			{
				error (1, errno, "%s", plc.nvm.name);
			}
			break;
		case 'P':
			if (!checkfilename (optarg))
			{
				error (1, EINVAL, "%s", optarg);
			}
			if ((plc.PIB.file = open (plc.PIB.name = optarg, O_BINARY|O_RDONLY)) == -1)
			{
				error (1, errno, "%s", plc.PIB.name);
			}
			if (pibfile2 (&plc.PIB))
			{
				error (1, errno, "Bad parameter file: %s", plc.PIB.name);
			}
			_setbits (plc.flags, PLC_WRITE_PIB);
			break;
		case 'p':
			if (!checkfilename (optarg))
			{
				error (1, EINVAL, "%s", optarg);
			}
			if ((plc.pib.file = open (plc.pib.name = optarg, O_BINARY|O_CREAT|O_RDWR|O_TRUNC, FILE_FILEMODE)) == -1)
			{
				error (1, errno, "%s", plc.pib.name);
			}
			break;
		case 'q':
			_setbits (channel.flags, CHANNEL_SILENCE);
			_setbits (plc.flags, PLC_SILENCE);
			break;
		case 'S':
			if (!checkfilename (optarg))
			{
				error (1, EINVAL, "%s", optarg);
			}
			if ((plc.CFG.file = open (plc.CFG.name = optarg, O_BINARY|O_RDONLY)) == -1)
			{
				error (1, errno, "%s", plc.CFG.name);
			}
			if (nvmfile2 (&plc.CFG))
			{
				error (1, errno, "Bad firmware file: %s", plc.CFG.name);
			}
			break;
		case 't':
			channel.timeout = (signed)(uintspec (optarg, 0, UINT_MAX));
			break;
		case 'v':
			_setbits (channel.flags, CHANNEL_VERBOSE);
			_setbits (plc.flags, PLC_VERBOSE);
			break;
		case 'w':
			plc.timer = (unsigned)(uintspec (optarg, 1, 3600));
			break;
		case 'x':
			_setbits (plc.flags, PLC_BAILOUT);
			break;
		default:
			break;
		}
	}
	argc -= optind;
	argv += optind;
	if (argc)
	{
		error (1, ENOTSUP, ERROR_TOOMANY);
	}
	if (plc.NVM.file == -1)
	{
		error (1, ECANCELED, "No host NVM file named");
	}
	if (plc.PIB.file == -1)
	{
		error (1, ECANCELED, "No host PIB file named");
	}
	if (plc.CFG.file == -1)
	{
		if (_anyset (plc.flags, PLC_FLASH_DEVICE))
		{
			error (1, ECANCELED, "No Softloader file named");
		}
	}
	if (plc.nvm.file == -1)
	{
		if ((plc.nvm.file = open (plc.nvm.name = NEWNVM, O_BINARY|O_CREAT|O_RDWR|O_TRUNC, FILE_FILEMODE)) == -1)
		{
			error (1, errno, "%s", plc.nvm.name);
		}
	}
	if (plc.pib.file == -1)
	{
		if ((plc.pib.file = open (plc.pib.name = NEWPIB, O_BINARY|O_CREAT|O_RDWR|O_TRUNC, FILE_FILEMODE)) == -1)
		{
			error (1, errno, "%s", plc.pib.name);
		}
	}

#ifndef WIN32

	if (_anyset (plc.flags, PLC_DAEMON))
	{
		pid_t pid = fork ();
		if (pid < 0)
		{
			error (1, errno, "Can't start daemon");
		}
		if (pid > 0)
		{
			exit (0);
		}
	}
	memset (&sa, 0, sizeof (struct sigaction));
	sa.sa_handler = terminate;
	sigaction (SIGTERM, &sa, (struct sigaction *)(0));
	sigaction (SIGQUIT, &sa, (struct sigaction *)(0));
	sigaction (SIGTSTP, &sa, (struct sigaction *)(0));
	sigaction (SIGINT, &sa, (struct sigaction *)(0));
	sigaction (SIGHUP, &sa, (struct sigaction *)(0));

#endif

	openchannel (&channel);
	if (!(plc.message = malloc (sizeof (* plc.message))))
	{
		error (1, errno, PLC_NOMEMORY);
	}
	function (&plc, socketname);
	free (plc.message);
	closechannel (&channel);
	exit (0);
}
Example #2
0
int main (int argc, char const * argv []) 

{
	extern struct channel channel;
	static char const * optv [] = 
	{
		"eFi:N:p:P:qS:t:vx",
		"-N file -P file [device] [device] [...]",
		"Qualcomm Atheros Panther/Lynx Powerline Device Bootstrapper",
		"e\tredirect stderr to stdout",
		"F[F]\tFlash [Force] non-volatile memory after boot",

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

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

#else

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

#endif

		"N f\tfirmware file is (f)",
		"P f\tparameter file is (f)",
		"q\tquiet mode",
		"S f\tsoftloader file is (f)",
		"t n\tread timeout is (n) milliseconds [" OPTSTR (CHANNEL_TIMEOUT) "]",
		"v\tverbose mode",
		"x\texit on error",
		(char const *) (0)
	};

#include "../plc/plc.c"

	char firmware [PLC_VERSION_STRING];
	signed c;
	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)) != -1) 
	{
		switch (c) 
		{
		case 'e':
			dup2 (STDOUT_FILENO, STDERR_FILENO);
			break;
		case 'F':
			_setbits (plc.module, (VS_MODULE_MAC | VS_MODULE_PIB));
			if (_anyset (plc.flags, PLC_FLASH_DEVICE)) 
			{
				_setbits (plc.module, VS_MODULE_FORCE);
			}
			_setbits (plc.flags, PLC_FLASH_DEVICE);
			break;
		case 'i':

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

			channel.ifindex = atoi (optarg);

#else

			channel.ifname = optarg;

#endif

			break;
		case 'N':
			if (!checkfilename (optarg)) 
			{
				error (1, EINVAL, "%s", optarg);
			}
			if ((plc.NVM.file = open (plc.NVM.name = optarg, O_BINARY|O_RDONLY)) == -1) 
			{
				error (1, errno, "%s", plc.NVM.name);
			}
			if (nvmfile2 (&plc.NVM)) 
			{
				error (1, errno, "Bad NVM file: %s", plc.NVM.name);
			}
			_setbits (plc.flags, PLC_WRITE_MAC);
			break;
		case 'P':
			if (!checkfilename (optarg)) 
			{
				error (1, EINVAL, "%s", optarg);
			}
			if ((plc.PIB.file = open (plc.PIB.name = optarg, O_BINARY|O_RDONLY)) == -1) 
			{
				error (1, errno, "%s", plc.PIB.name);
			}
			if (pibfile2 (&plc.PIB)) 
			{
				error (1, errno, "Bad PIB file: %s", plc.PIB.name);
			}
			_setbits (plc.flags, PLC_WRITE_PIB);
			break;
		case 'q':
			_setbits (channel.flags, CHANNEL_SILENCE);
			_setbits (plc.flags, PLC_SILENCE);
			break;
		case 'S':
			if (!checkfilename (optarg)) 
			{
				error (1, EINVAL, "%s", optarg);
			}
			if ((plc.CFG.file = open (plc.CFG.name = optarg, O_BINARY|O_RDONLY)) == -1) 
			{
				error (1, errno, "%s", plc.CFG.name);
			}
			if (nvmfile2 (&plc.CFG)) 
			{
				error (1, errno, "Bad NVM file: %s", plc.CFG.name);
			}
			break;
		case 't':
			channel.timeout = (signed)(uintspec (optarg, 0, UINT_MAX));
			break;
		case 'v':
			_setbits (channel.flags, CHANNEL_VERBOSE);
			_setbits (plc.flags, PLC_VERBOSE);
			break;
		case 'x':
			_setbits (plc.flags, PLC_BAILOUT);
			break;
		default:
			break;
		}
	}
	argc -= optind;
	argv += optind;
	if (argc) 
	{
		error (1, ENOTSUP, ERROR_TOOMANY);
	}
	openchannel (&channel);
	if (!(plc.message = malloc (sizeof (* plc.message)))) 
	{
		error (1, errno, PLC_NOMEMORY);
	}
	if (WaitForStart (&plc, firmware, sizeof (firmware))) 
	{
		Failure (&plc, PLC_NODETECT);
		exit (1);
	}
	if (plc.hardwareID < CHIPSET_INT6400) 
	{
		Failure (&plc, "Device must be %s or later; Use program int6kboot instead.", chipsetname (CHIPSET_INT6400));
		exit (1);
	}
	if (strcmp (firmware, "BootLoader")) 
	{
		Failure (&plc, "Bootloader must be running");
		exit (1);
	}
	if (plc.PIB.file == -1) 
	{
		error (1, ECANCELED, "No Parameter file named");
	}
	if (plc.NVM.file == -1) 
	{
		error (1, ECANCELED, "No Firmware file named");
	}
	if (plc.CFG.file == -1) 
	{
		if (_anyset (plc.flags, PLC_FLASH_DEVICE)) 
		{
			error (1, ECANCELED, "No Softloader file named");
		}
	}
	if (!InitDevice2 (&plc)) 
	{
		if (!BootDevice2 (&plc)) 
		{
			if (_anyset (plc.flags, PLC_FLASH_DEVICE)) 
			{
				FlashDevice2 (&plc);
			}
		}
	}
	free (plc.message);
	closechannel (&channel);
	exit (0);
}
Example #3
0
int main (int argc, char const * argv []) 

{
	extern struct channel channel;
	extern const struct key keys [];
	static char const * optv [] = 
	{
		"abB:d:D:efFHi:IJ:K:l:mMn:N:p:P:QqrRS:t:Tvw:x",
		"device [device] [...]",
		"Qualcomm Atheros Panther/Lynx Powerline Device Manager",
		"a\tread Device Attributes using VS_OP_ATTRIBUTES",
		"B n\tperform pushbutton action (n) using MS_PB_ENC [1|2|3|'join'|'leave'|'status']",
		"d f\tdump and clear watchdog report to file (f) using VS_WD_RPT",
		"D x\tDevice Access Key (DAK) is (x) [" DAK1 "]",
		"e\tredirect stderr to stdout",
		"f\tread NVRAM Configuration using VS_GET_NVM",
		"F[F]\tflash [force] parameters and/or firmware using VS_MODULE_OPERATION",
		"H\tstop host action requests using VS_HST_ACTION.IND",

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

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

#else

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

#endif

		"I\tread device identity using VS_MODULE_OPERATION",
		"J x\tset NMK on remote device (x) via local device using VS_SET_KEY (see -K)",
		"K x\tNetwork Membership Key (NMK) is (x) [" NMK1 "]",
		"l n\tloop (n) times [" OPTSTR (PLCTOOL_LOOP) "]",
		"m\tread network membership information using VS_NW_INFO",
		"M\tset NMK on local device using VS_SET_KEY (see -K)",
		"n f\tread NVM from SDRAM to file (f) using VS_MODULE_OPERATION",
		"N f\twrite firmware file (f) to flash memory using VS_MODULE_OPERATION",
		"p f\tread PIB from SDRAM to file (f) using VS_MODULE_OPERATION",
		"P f\twrite parameter file (f) to flash memory using VS_MODULE_OPERATION",
		"q\tquiet mode",
		"Q\tquick flash (return immediately)",
		"r\tread hardware and firmware revision using VS_SW_VER",
		"R\treset device using VS_RS_DEV",
		"S f\twrite softloader file (f) to flash memory using VS_MODULE_OPERATION",
		"t n\tread timeout is (n) milliseconds [" OPTSTR (CHANNEL_TIMEOUT) "]",
		"T\trestore factory defaults using VS_FAC_DEFAULTS",
		"v\tverbose mode",
		"w n\tpause (n) seconds [" OPTSTR (PLCTOOL_WAIT) "]",
		"x\texit on error",
		(char const *) (0)
	};

#include "../plc/plc.c"

	signed loop = PLCTOOL_LOOP;
	signed wait = PLCTOOL_WAIT;
	signed c;
	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)) != -1) 
	{
		switch (c) 
		{
		case 'a':
			_setbits (plc.flags, PLC_ATTRIBUTES);
			break;
		case 'B':
			_setbits (plc.flags, PLC_PUSH_BUTTON);
			plc.pushbutton = (unsigned)(uintspec (synonym (optarg, buttons, BUTTONS), 0, UCHAR_MAX));
			break;
		case 'b':
			_setbits (plc.flags, PLC_REMOTEHOSTS);
			break;
		case 'd':
			_setbits (plc.flags, PLC_WATCHDOG_REPORT);
			if (!checkfilename (optarg)) 
			{
				error (1, EINVAL, "%s", optarg);
			}
			if ((plc.rpt.file = open (plc.rpt.name = optarg, O_BINARY|O_CREAT|O_RDWR|O_TRUNC, FILE_FILEMODE)) == -1) 
			{
				error (1, errno, "%s", plc.rpt.name);
			}

#ifndef WIN32

			chown (optarg, getuid (), getgid ());

#endif

			plc.readaction = 3;
			break;
		case 'D':
			if (!strcmp (optarg, "none")) 
			{
				memcpy (plc.DAK, keys [0].DAK, sizeof (plc.DAK));
				break;
			}
			if (!strcmp (optarg, "key1")) 
			{
				memcpy (plc.DAK, keys [1].DAK, sizeof (plc.DAK));
				break;
			}
			if (!strcmp (optarg, "key2")) 
			{
				memcpy (plc.DAK, keys [2].DAK, sizeof (plc.DAK));
				break;
			}
			if (!hexencode (plc.DAK, sizeof (plc.DAK), (char const *)(optarg))) 
			{
				error (1, errno, PLC_BAD_DAK, optarg);
			}
			break;
		case 'e':
			dup2 (STDOUT_FILENO, STDERR_FILENO);
			break;
		case 'f':
			_setbits (plc.flags, PLC_NVRAM_INFO);
			break;
		case 'F':
			_setbits (plc.module, (VS_MODULE_MAC | VS_MODULE_PIB));
			if (_anyset (plc.flags, PLC_FLASH_DEVICE)) 
			{
				_setbits (plc.module, VS_MODULE_FORCE);
			}
			_setbits (plc.flags, PLC_FLASH_DEVICE);
			break;
		case 'H':
			_setbits (plc.flags, PLC_HOST_ACTION);
			break;
		case 'I':
			_setbits (plc.flags, PLC_READ_IDENTITY);
			break;
		case 'i':

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

			channel.ifindex = atoi (optarg);

#else

			channel.ifname = optarg;

#endif

			break;
		case 'J':
			if (!hexencode (plc.RDA, sizeof (plc.RDA), (char const *)(optarg))) 
			{
				error (1, errno, PLC_BAD_MAC, optarg);
			}
			_setbits (plc.flags, PLC_SETREMOTEKEY);
			break;
		case 'K':
			if (!strcmp (optarg, "none")) 
			{
				memcpy (plc.NMK, keys [0].NMK, sizeof (plc.NMK));
				break;
			}
			if (!strcmp (optarg, "key1")) 
			{
				memcpy (plc.NMK, keys [1].NMK, sizeof (plc.NMK));
				break;
			}
			if (!strcmp (optarg, "key2")) 
			{
				memcpy (plc.NMK, keys [2].NMK, sizeof (plc.NMK));
				break;
			}
			if (!hexencode (plc.NMK, sizeof (plc.NMK), (char const *)(optarg))) 
			{
				error (1, errno, PLC_BAD_NMK, optarg);
			}
			break;
		case 'M':
			_setbits (plc.flags, PLC_SETLOCALKEY);
			break;
		case 'l':
			loop = (unsigned)(uintspec (optarg, 0, UINT_MAX));
			break;
		case 'm':
			_setbits (plc.flags, PLC_NETWORK);
			break;
		case 'N':
			if (!checkfilename (optarg)) 
			{
				error (1, EINVAL, "%s", optarg);
			}
			if ((plc.NVM.file = open (plc.NVM.name = optarg, O_BINARY|O_RDONLY)) == -1) 
			{
				error (1, errno, "%s", plc.NVM.name);
			}
			if (nvmfile2 (&plc.NVM)) 
			{
				error (1, errno, "Bad image file: %s", plc.NVM.name);
			}
			_setbits (plc.flags, PLC_FLASH_DEVICE);
			break;
		case 'n':
			if (!checkfilename (optarg)) 
			{
				error (1, EINVAL, "%s", optarg);
			}
			if ((plc.nvm.file = open (plc.nvm.name = optarg, O_BINARY|O_CREAT|O_RDWR|O_TRUNC, FILE_FILEMODE)) == -1) 
			{
				error (1, errno, "%s", plc.nvm.name);
			}

#ifndef WIN32

			chown (optarg, getuid (), getgid ());

#endif

			_setbits (plc.flags, PLC_READ_MAC);
			break;
		case 'P':
			if (!checkfilename (optarg)) 
			{
				error (1, EINVAL, "%s", optarg);
			}
			if ((plc.PIB.file = open (plc.PIB.name = optarg, O_BINARY|O_RDONLY)) == -1) 
			{
				error (1, errno, "%s", plc.PIB.name);
			}
			if (pibfile2 (&plc.PIB)) 
			{
				error (1, errno, "Bad image file: %s", plc.PIB.name);
			}
			_setbits (plc.flags, PLC_FLASH_DEVICE);
			break;
		case 'p':
			if (!checkfilename (optarg)) 
			{
				error (1, EINVAL, "%s", optarg);
			}
			if ((plc.pib.file = open (plc.pib.name = optarg, O_BINARY|O_CREAT|O_RDWR|O_TRUNC, FILE_FILEMODE)) == -1) 
			{
				error (1, errno, "%s", plc.pib.name);
			}

#ifndef WIN32

			chown (optarg, getuid (), getgid ());

#endif

			_setbits (plc.flags, PLC_READ_PIB);
			break;
		case 'Q':
			_setbits (plc.flags, PLC_QUICK_FLASH);
			break;
		case 'q':
			_setbits (channel.flags, CHANNEL_SILENCE);
			_setbits (plc.flags, PLC_SILENCE);
			break;
		case 'R':
			_setbits (plc.flags, PLC_RESET_DEVICE);
			break;
		case 'r':
			_setbits (plc.flags, PLC_VERSION);
			break;
		case 'S':
			if (!checkfilename (optarg)) 
			{
				error (1, EINVAL, "%s", optarg);
			}
			if ((plc.CFG.file = open (plc.CFG.name = optarg, O_BINARY|O_RDONLY)) == -1) 
			{
				error (1, errno, "%s", plc.CFG.name);
			}
			if (nvmfile2 (&plc.CFG)) 
			{
				error (1, errno, "Bad image file: %s", plc.CFG.name);
			}
			_setbits (plc.flags, PLC_FLASH_DEVICE);
			break;
		case 't':
			channel.timeout = (signed)(uintspec (optarg, 0, UINT_MAX));
			break;
		case 'T':
			_setbits (plc.flags, PLC_FACTORY_DEFAULTS);
			break;
		case 'v':
			_setbits (channel.flags, CHANNEL_VERBOSE);
			_setbits (plc.flags, PLC_VERBOSE);
			break;
		case 'V':
			_setbits (plc.flags, PLC_SNIFFER);
			plc.action = (uint8_t)(uintspec (optarg, 0, UCHAR_MAX));
			break;
		case 'w':
			wait = (unsigned)(uintspec (optarg, 0, 3600));
			break;
		case 'x':
			_setbits (plc.flags, PLC_BAILOUT);
			break;
		default:
			break;
		}
	}
	argc -= optind;
	argv += optind;
	if (argc != 1) 
	{
		if (plc.nvm.file != -1) 
		{
			error (1, ECANCELED, PLC_NODEVICE);
		}
		if (plc.pib.file != -1) 
		{
			error (1, ECANCELED, PLC_NODEVICE);
		}
		if (plc.rpt.file != -1) 
		{
			error (1, ECANCELED, PLC_NODEVICE);
		}
	}
	openchannel (&channel);
	if (!(plc.message = malloc (sizeof (* plc.message)))) 
	{
		error (1, errno, PLC_NOMEMORY);
	}
	if (!argc) 
	{
		manager (&plc, loop, wait);
	}
	while ((argc) && (* argv)) 
	{
		if (!hexencode (channel.peer, sizeof (channel.peer), synonym (* argv, devices, SIZEOF (devices)))) 
		{
			error (1, errno, PLC_BAD_MAC, * argv);
		}
		manager (&plc, loop, wait);
		argc--;
		argv++;
	}
	free (plc.message);
	closechannel (&channel);
	exit (0);
}