/*****************************************************************************/

/*
 *      baycomusb.c  -- HDLC packet radio modem for USB using FPGA/AnchorChips utility.
 *
 *      Copyright (C) 1998-2001  Thomas Sailer (t.sailer@alumni.ethz.ch)
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  Please note that the GPL allows you to use the driver, NOT the radio.
 *  In order to use the radio, you need a license from the communications
 *  authority of your country.
 *
 */

/*****************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifndef WIN32
#include <sys/socket.h>
#include <sys/ioctl.h>
#endif

#include <stdarg.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#else
#include "getopt.h"
#endif

#ifdef HAVE_SYSLOG_H
#include <syslog.h>
#endif

#ifdef HAVE_LINUX_IF_H
#include <linux/if.h>
#endif
#ifdef HAVE_LINUX_AX25_H
#include <linux/ax25.h>
#endif
#ifdef HAVE_LINUX_SOCKIOS_H
#include <linux/sockios.h>
#endif
#ifdef HAVE_LINUX_IF_ETHER_H
#include <linux/if_ether.h>
#endif
#if defined(HAVE_ARPA_INET_H) && !defined(WIN32)
#include <arpa/inet.h>
#endif

#include "baycomusb.h"

/* ---------------------------------------------------------------------- */

static int quit = 0;

/* ---------------------------------------------------------------------- */

int tprintf(const char *format, ...)
{
	va_list ap;
	int i;

        va_start(ap, format);
        i = vprintf(format, ap);
        va_end(ap);
	fflush(stdout);
	return i;
}

int idle_callback(unsigned us_delay)
{
	if (us_delay)
		usleep(us_delay);
	return quit;
}

static void signal_quit(int signum)
{
	quit = 1;
}

/* ---------------------------------------------------------------------- */

#ifdef WIN32

static int doinit(int devnr, int busnr, const char *ifname)
{
	return 0;
}

#else /* WIN32 */

static int parsehw(ax25_address *hwaddr, const char *cp)
{
        const char *cp1;
        unsigned int i, j;

        if (!cp || !*cp)
                return 0;
        memset(hwaddr->ax25_call, ' ', 6);
        cp1 = strchr(cp, '-');
        if (cp1) {
                i = cp1 - cp;
                j = strtoul(cp1 + 1, NULL, 0);
                hwaddr->ax25_call[6] = j & 15;
        } else {
                i = strlen(cp);
		hwaddr->ax25_call[6] = 0;
	}
        if (i > 6)
                i = 6;
        memcpy(hwaddr->ax25_call, cp, i);
        for (i = 0; i < 7; i++)
                hwaddr->ax25_call[i] <<= 1;
        return 1;
}

static int parseip(struct in_addr *ipaddr, const char *cp)
{
        if (!cp || !*cp)
                return 0;
        if (inet_aton(cp, ipaddr))
                return 1;
        ipaddr->s_addr = 0;
        lprintf(0, "baycomusb: invalid IP address \"%s\"\n", cp);
        return 0;
}

#define PARAM_TXDELAY   1
#define PARAM_PERSIST   2
#define PARAM_SLOTTIME  3
#define PARAM_TXTAIL    4
#define PARAM_FULLDUP   5

static int initonedev(struct usbdevice *dev, const char *ifname)
{
	struct configentry *cfg;
	struct sockaddr sa;
	struct sockaddr_ax25 sax25;
        struct sockaddr_in sin;
        struct ifreq ifr, ifr_h;
	unsigned char buf[2];
  	int i, fd;

	i = adapter_configure(dev);
	if (i < 1)
		return i;
	cfg = adapter_findconfig(dev);
	strncpy(ifr_h.ifr_name, ifname, sizeof(ifr_h.ifr_name));
#ifdef HAVE_IFRNEWNAME
	if (cfg->ifcfg.ifname[0]) {
		if ((fd = socket(PF_AX25, SOCK_SEQPACKET, 0)) < 0) {
			lprintf(0, "Cannot create socket: %s (%u) \n", strerror(errno), errno);
	       	} else {
			strncpy(ifr_h.ifr_newname, cfg->ifcfg.ifname, sizeof(ifr_h.ifr_newname));
			if (ioctl(fd, SIOCSIFNAME, &ifr_h) == -1)
				lprintf(0, "Cannot rename interface (SIOCSIFNAME: %s (%u))\n", strerror(errno), errno);
			else
				strncpy(ifr_h.ifr_name, cfg->ifcfg.ifname, sizeof(ifr_h.ifr_name));
			close(fd);
		}
	}
#endif
	if ((fd = socket(/*PF_PACKET*/AF_INET, /*SOCK_RAW*/SOCK_PACKET, htons(ETH_P_AX25))) < 0) {
		lprintf(0, "Cannot create socket: %s (%u) \n", strerror(errno), errno);
		return -1;
	}
	if (ioctl(fd, SIOCGIFINDEX, &ifr_h) == -1)
		lprintf(0, "ioctl: SIOCGIFINDEX failed: %s (%u)\n", strerror(errno), errno);
	ifr = ifr_h;
	ifr.ifr_mtu = 256;
	if (ioctl(fd, SIOCSIFMTU, &ifr) == -1)
		lprintf(0, "ioctl: SIOCSIFMTU failed: %s (%u)\n", strerror(errno), errno);
	if (parsehw(&sax25.sax25_call, cfg->ifcfg.hwaddr)) {
                sax25.sax25_family = AF_AX25;
                sax25.sax25_ndigis = 0;
		ifr = ifr_h;
                memcpy(&ifr.ifr_hwaddr, &sax25, sizeof(ifr.ifr_hwaddr));
                if (ioctl(fd, SIOCSIFHWADDR, &ifr) == -1)
                        lprintf(0, "ioctl: SIOCSIFHWADDR failed: %s (%u)\n", strerror(errno), errno);
	}
	if (parseip(&sin.sin_addr, cfg->ifcfg.ipaddr)) {
                sin.sin_family = AF_INET;
		ifr = ifr_h;
                memcpy(&ifr.ifr_addr, &sin, sizeof(ifr.ifr_addr));
                if (ioctl(fd, SIOCSIFADDR, &ifr) == -1)
                        lprintf(0, "ioctl: SIOCSIFADDR failed: %s (%u)\n", strerror(errno), errno);
        }
	if (parseip(&sin.sin_addr, cfg->ifcfg.netmask)) {
                sin.sin_family = AF_INET;
		ifr = ifr_h;
                memcpy(&ifr.ifr_addr, &sin, sizeof(ifr.ifr_addr));
                if (ioctl(fd, SIOCSIFNETMASK, &ifr) == -1)
                        lprintf(0, "ioctl: SIOCSIFNETMASK failed: %s (%u)\n", strerror(errno), errno);
        }
	if (parseip(&sin.sin_addr, cfg->ifcfg.broadcast)) {
                sin.sin_family = AF_INET;
		ifr = ifr_h;
                memcpy(&ifr.ifr_addr, &sin, sizeof(ifr.ifr_addr));
                if (ioctl(fd, SIOCSIFBRDADDR, &ifr) == -1)
                        lprintf(0, "ioctl: SIOCSIFBRDADDR failed: %s (%u)\n", strerror(errno), errno);
        }
	if (cfg->adapter.mode != MODE_AUDIO) {
		ifr = ifr_h;
		if (ioctl(fd, SIOCGIFFLAGS, &ifr) == -1)
			lprintf(0, "ioctl: SIOCGIFFLAGS failed: %s (%u)\n", strerror(errno), errno);
		else {
			ifr.ifr_flags &= ~IFF_NOARP;
			ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
			if (ioctl(fd, SIOCSIFFLAGS, &ifr) == -1)
				lprintf(0, "ioctl: SIOCSIFFLAGS failed: %s (%u)\n", strerror(errno), errno);
		}
	}
	strncpy(sa.sa_data, ifr_h.ifr_name, sizeof(sa.sa_data));
	sa.sa_family = AF_PACKET;
	buf[0] = PARAM_TXDELAY;
	buf[1] = cfg->chaccess.txdelay / 10;
	if (sendto(fd, buf, 2, 0, &sa, sizeof(struct sockaddr)) == -1)
		lprintf(0, "sendto failed: %s (%u)\n", strerror(errno), errno);
	buf[0] = PARAM_PERSIST;
	buf[1] = cfg->chaccess.ppersistence;
	if (sendto(fd, buf, 2, 0, &sa, sizeof(sa)) == -1)
		lprintf(0, "sendto failed: %s (%u)\n", strerror(errno), errno);
	buf[0] = PARAM_SLOTTIME;
	buf[1] = cfg->chaccess.slottime / 10;
	if (sendto(fd, buf, 2, 0, &sa, sizeof(sa)) == -1)
		lprintf(0, "sendto failed: %s (%u)\n", strerror(errno), errno);
	buf[0] = PARAM_TXTAIL;
	buf[1] = cfg->chaccess.txtail / 10;
	if (sendto(fd, buf, 2, 0, &sa, sizeof(sa)) == -1)
		lprintf(0, "sendto failed: %s (%u)\n", strerror(errno), errno);
	buf[0] = PARAM_FULLDUP;
	buf[1] = cfg->chaccess.fullduplex;
	if (sendto(fd, buf, 2, 0, &sa, sizeof(sa)) == -1)
		lprintf(0, "sendto failed: %s (%u)\n", strerror(errno), errno);
	return 0;
}

static int doinit(int devnr, int busnr, const char *ifname)
{
	struct usbdevice *dev;
	unsigned int index;
	int ret;

	if (devnr != -1 && busnr != -1) {
		if (!(dev = usb_open_bynumber(busnr, devnr, BAYCOMUSB_VENDORID, -1))) {
			lprintf(0, "Cannot open Baycom USB device %03u:%03u\n", busnr, devnr);
			return -1;
		}
		ret = initonedev(dev, ifname);
		usb_close(dev);
		return ret;
	}
	for (ret = index = 0; ; index++) {
		if (!(dev = usb_open(BAYCOMUSB_VENDORID, -1, 0, index))) {
			if (!index)
				lprintf(0, "No Baycom USB devices found\n");
			break;
		}
		ret |= initonedev(dev, ifname);
		usb_close(dev);
	}
	return ret;
}

#endif /* WIN32 */

static int dotest(unsigned int index, int devnr, int busnr, const struct usbtests *tst)
{
	struct usbdevice *dev;
	struct configentry *cfg;
	unsigned int i;
	int ret;

	for (i = 0; ; i++) {
		cfg = config_entry(i);
		if (!cfg)
			break;
		tst->cfg(cfg);
	}
	if (devnr != -1 && busnr != -1) {
		if (!(dev = usb_open_bynumber(busnr, devnr, BAYCOMUSB_VENDORID, -1))) {
			lprintf(0, "Cannot open Baycom USB device %03u:%03u\n", busnr, devnr);
			return -1;
		}
	} else {
		if (!(dev = usb_open(BAYCOMUSB_VENDORID, -1, 0, index))) {
			lprintf(0, "Cannot open Baycom USB device at index %u\n", index);
			return -1;
		}
	}
	cfg = config_entry(0);
	if (cfg->adapter.mode != MODE_NONE) {
		ret = adapter_configure(dev);
		if (ret == -1)
			goto err;
		if (!ret) {
			usb_close(dev);
			sleep(2);
			if (devnr != -1 && busnr != -1) {
				if (!(dev = usb_open_bynumber(busnr, devnr, BAYCOMUSB_VENDORID, -1))) {
					lprintf(0, "Cannot open Baycom USB device %03u:%03u\n", busnr, devnr);
					return -1;
				}
			} else {
				if (!(dev = usb_open(BAYCOMUSB_VENDORID, -1, 0, index))) {
					lprintf(0, "Cannot open Baycom USB device at index %u\n", index);
					return -1;
				}
			}
			ret = adapter_configure(dev);
			if (ret < 1) {
				ret = -1;
				goto err;
			}
		}
	}
#ifdef SIGHUP
	signal(SIGHUP, signal_quit);
#endif
	signal(SIGINT, signal_quit);
#ifdef SIGQUIT
	signal(SIGQUIT, signal_quit);
#endif
	signal(SIGTERM, signal_quit);
	ret = abs(tst->func(dev, adapter_findconfig(dev)));
 err:
	usb_close(dev);
	return ret;
}

/* ---------------------------------------------------------------------- */

int main(int argc, char *argv[])
{
        static const struct option long_options[] = {
                { "drv", 1, 0, 'D' },
		{ "config", 1, 0, 'C' },
                { "device", 1, 0, 'd' },
                { "index", 1, 0, 'i' },
                { "ifname", 1, 0, 'I' },
                { "test", 1, 0, 't' },
                { "verbose", 0, 0, 'v' },
                { "help", 0, 0, 'h' },
		{ 0, 0, 0, 0 }
        };
	int c, err = 0;
	const struct usbtests *tst = NULL;
	const char *ifname = NULL;
	int devnr = -1, busnr = -1;
	unsigned int index = 0;
	char *cp;

	printf("baycomusb v" VERSION " (c) 1999-2001 by Thomas Sailer, HB9JNX/AE4WA\n");
	while ((c = getopt_long(argc, argv, "D:C:svd:i:I:t:", long_options, NULL)) != EOF) {
		switch (c) {
		case 'D':
			usb_setmountpoint(optarg);
			break;

		case 'C':
			config_setfile(optarg);
			break;

#if defined(HAVE_SYSLOG) && defined(HAVE_VSYSLOG)
		case 's':
			if (syslogmsg)
				break;
			openlog("baycomusb", LOG_PID, LOG_USER);
			syslogmsg = 1;
			break;
#endif

		case 'v':
			verboselevel++;
			break;

		case 'd':
			cp = strchr(optarg, ':');
			if (!cp) {
				busnr = 0;
				devnr = strtoul(optarg, NULL, 0);
				break;
			}
			*cp++ = 0;
			busnr = strtoul(optarg, NULL, 0);
			devnr = strtoul(cp, NULL, 0);
			break;

		case 'i':
			index = strtoul(optarg, NULL, 0);
			break;

		case 'I':
			ifname = optarg;
			break;

		case 't':
			if (tst != NULL) {
				err++;
				break;
			}
			for (tst = usbtests; tst->name && strcmp(optarg, tst->name); tst++);
			if (!tst->name)
				err++;
			break;

		default:
			err++;
			break;
		}
	}
	if (err) {
		lprintf(0, "usage: %s [-v] [-D <drvpath>] [-C <configfile>] [-d <bus:dev>] [-i <idx>] [-I <ifname>] [-t <test>]\n"
			"  test options:\n", argv[0]);
		for (tst = usbtests; tst->name; tst++)
			lprintf(0, "    %-15s%s\n", tst->name, tst->desc);
		exit(1);
	}
	if (config_parse())
		lprintf(0, "Warning: Could not parse configuration file %s\n", config_getfile());
	if (!tst)
		exit(doinit(devnr, busnr, ifname));
	exit(dotest(index, devnr, busnr, tst));
}
