/*
 *      set_oeminfo.c from Access Point SNMP Utils for Linux
 *
 * Copyright (c) 2005 Jan Rafaj <jr-aputils at cedric dot unob dot cz>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2 from
 * June 1991 as published by the Free Software Foundation.
 *
 * 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.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include "ap-utils.h"

#define S_STRUCTVERSION _("Info structure version: ")
#define S_MAC _("[M] Device MAC address: ")
#define V_OUI _("    Manufacturer with this OUI: ")
#define S_REGDOM _("[D] Regulatory domain: ")
#define S_PRODTYPE _("[T] Product type: ")
#define S_OEMNAME _("[E] OEM name: ")
#define S_OEMID _("[I] OEM ID: ")
#define S_PRODNAME _("[N] Product name: ")
#define S_HWREV _("[H] Hardware revision: ")
#define S_COUNTRYC _("[O] Country code: ")
#define S_DEFCHAN _("[C] Default channel: ")
#define S_CHANNELS _("[A] Calibrated channels: ")
#define S_TXPOWER _("[P] Nominal Tx Power (CR31) value for calibrated channels: ")
#define OEMSET_HELP _("Keys in brackets - set corresponding option; W - write conf; Q - quit to menu")

extern WINDOW *main_sub;
extern short ap_type;
extern char *channels[];
extern rdprops regdom_types[];

// TODO: test & fix for big-endian archs

void atmel_set_oeminfo()
{
    char sysDeviceInfo[] = {
	0x2B, 0x06, 0x01, 0x04, 0x01, 0x83, 0x1A, 0x01, 0x01, 0x01, 0x05, 0x00
    };
    // Following one is only available in newer ATMEL12350 MIBs
    char sysDeviceMoreInfo[] = {
	0x2B, 0x06, 0x01, 0x04, 0x01, 0xE0, 0x3E, 0x01, 0x01, 0x01, 0x08, 0x00
    };

    char message[200], c;
    char **regdoms;
    int i, rd_idx, st_len;
    varbind varbinds[1];
    struct sysDeviceInfo_128 str128; /* SHORTst */
    struct sysDeviceInfo_160 str160; /* LONGst */

    /*
     * These have to represent used sysDeviceInfo_{128,160} members
     * Note that sysOIDSize, sysOID and PID_VID can NOT be written
     * using sysDevice*Info OIDs at all - the only way to change them
     * (highly discouraged !) is in OEM configuration file, using AP RFMD
     * or AP INTERSIL configuration editor, followed by uploading this file
     * to the AP using ATMEL tftp client - this way, *all* defaults
     * can be changed.
     */
    uint32_t StructVersion;	// [4]
    char *MacAddress;		// [6]
				// [2 reserved for SHORTst; 0 for LONGst] 
    char Channel = 0; // LONGst specific: [0 for SHORTst; 1 for LONGst]
    uint32_t RegulatoryDomain;	// [4 for SHORTst; 1 for LONGst]
    uint32_t ProductType;	// [4]
    char *OEMName;		// [32]
    uint32_t OEMID;		// [4]
    char *ProductName;		// [32]
    uint32_t HardwareRevision;	// [4]
    /* the rest is LONGst specific */
    char *PID_VID = NULL;	// [0 for SHORTst; 4 for LONGst]
    uint32_t sysOIDSize = 0;	// [0 for SHORTst; 4 for LONGst]
    uint16_t *sysOID = NULL;	// [0 for SHORTst; 32 for LONGst]
    char *CountryCode = NULL;	// [0 for SHORTst; 3 for LONGst]
				// [0 for SHORTst; 1 reserved for LONGst]
    uint16_t ChannelInformation = 0; // [0 for SHORTst; 2 for LONGst]
				// [0 for SHORTst; 2 reserved for LONGst]
    char *TxPower = NULL;	// [0 for SHORTst; 14 for LONGst]
				// [0 for SHORTst; 10 reserved for LONGst]
    clear_main(0);
    noecho();

    wattrset(main_sub, A_BOLD);
    mvwaddstr(main_sub, 2, 4,
	_("THIS IS A SECRET MENU LEADING TO VERY DANGEROUS OPTIONS."));
    mvwaddstr(main_sub, 3, 8,
	_("It is intended only for WISPs and repair shops."));
    wattrset(main_sub, A_NORMAL);

    mvwaddstr(main_sub, 5, 1,
	_("It allows to set OEM information stored in the AP (like its"));
    mvwaddstr(main_sub, 6, 1,
	_("MAC address, manuf. name, OEM ID, etc.). BE ABSOLUTELY SURE"));
    mvwaddstr(main_sub, 7, 1,
	_("THAT YOU KNOW WHAT YOU ARE DOING, AND THAT YOU HAVE THE"));
    mvwaddstr(main_sub, 8, 1,
	_("LEGAL RIGHT TO DO ANY MODIFICATION. Disobserving of these"));
    mvwaddstr(main_sub, 9, 1,
	_("rules may lead you into a conflict with your local"));
    mvwaddstr(main_sub, 10, 1,
	_("regulations and/or law."));
    mvwaddstr(main_sub, 11, 1,
	_("Also be warned that the setting of any from these options"));
    mvwaddstr(main_sub, 12, 1,
	_("may DAMAGE YOUR AP (other reason why this menu is hidden)."));
    mvwaddstr(main_sub, 13, 1,
	_("Values changed here will NOT be restored upon reset"));
    mvwaddstr(main_sub, 14, 1,
	_("of the device to factory defaults! It is advisable to"));
    mvwaddstr(main_sub, 15, 1,
	_("write them down somewhere prior their changing."));
    mvwaddstr(main_sub, 16, 1,
	_("Finally, note that you need to use MANUFACTURER community, "));
    mvwaddstr(main_sub, 17, 1,
	_("in order to be able to do any OEM info modification."));
    mvwaddstr(main_sub, 18, 1,
	_("Proceed further only at your full risk and responsibility. "));
    mvwaddstr(main_sub, 19, 1,
	_("You got the warnings."));
    wrefresh(main_sub);

    if (help_ysn())
	return;

    print_top(NULL, _("OEM Info settings"));

    if (ap_type == ATMEL12350) {
	sysDeviceInfo[5] = 0xE0;
	sysDeviceInfo[6] = 0x3E;
    }

    varbinds[0].oid = sysDeviceInfo;
    varbinds[0].len_oid = sizeof(sysDeviceInfo);
    varbinds[0].value = sysDeviceInfo;
    varbinds[0].len_val = 0;
    varbinds[0].type = NULL_VALUE;

    print_help(WAIT_RET);
    if (snmp(varbinds, 1, GET) <= 0) {
	print_helperr(ERR_RET);
	goto exit;
    }

    st_len = varbinds[0].len_val;

    if (st_len == 92 || st_len == 128) { /* SHORTst */
	memcpy(&str128, varbinds[0].value,
	    sizeof(struct sysDeviceInfo_128));
	StructVersion		= str128.StructVersion;
	MacAddress		= str128.MacAddress;
	RegulatoryDomain	= str128.RegulatoryDomain;
	ProductType		= str128.ProductType;
	OEMName			= str128.OEMName;
	OEMID			= str128.OEMID;
	ProductName		= str128.ProductName;
	HardwareRevision	= str128.HardwareRevision;
    } else { /* st_len == 160 => LONGst */
	memcpy(&str160, varbinds[0].value,
	    sizeof(struct sysDeviceInfo_160));
	StructVersion		= str160.StructVersion;
	MacAddress		= str160.MacAddress;
	Channel			= str160.Channel;
	RegulatoryDomain	= str160.RegulatoryDomain;
	ProductType		= str160.ProductType;
	OEMName			= str160.OEMName;
	OEMID			= str160.OEMID;
	ProductName		= str160.ProductName;
	HardwareRevision	= str160.HardwareRevision;
	PID_VID			= str160.PID_VID;
	sysOIDSize		= str160.sysOIDSize;
	sysOID			= str160.sysOID;
	CountryCode		= str160.CountryCode;
	ChannelInformation	= str160.ChannelInformation;
	TxPower			= str160.TxPower;
    }

    clear_main(0);

    sprintf(message, "%s%u", S_STRUCTVERSION, StructVersion);
    mvwaddstr(main_sub, 0, 0, message);

    sprintf(message, "%s%02X%02X%02X%02X%02X%02X", S_MAC,
	MacAddress[0] & 0xFF, MacAddress[1] & 0xFF, MacAddress[2] & 0xFF,
	MacAddress[3] & 0xFF, MacAddress[4] & 0xFF, MacAddress[5] & 0xFF);
    mvwaddstr(main_sub, 1, 0, message);
    sprintf(message, "%s%s", V_OUI, oui2manufacturer(MacAddress));
    mvwaddstr(main_sub, 2, 0, message);

    rd_idx = regdom_idx(RegulatoryDomain);
    sprintf(message, "%s%s [%d]", S_REGDOM,
	regdom_types[rd_idx].desc, RegulatoryDomain);
    mvwaddstr(main_sub, 3, 0, message);

    mvwaddstr(main_sub, 4, 0, S_OEMNAME);
    for (i = 0; i < 32 && OEMName[i]; i++)
	waddch(main_sub, OEMName[i]);

    sprintf(message, "%s%u", S_OEMID, OEMID);
    mvwaddstr(main_sub, 5, 0, message);

    mvwaddstr(main_sub, 6, 0, S_PRODNAME);
    for (i = 0; i < 32 && ProductName[i]; i++)
	waddch(main_sub, ProductName[i]);

    sprintf(message, _("%s%u"), S_PRODTYPE, ProductType);
    mvwaddstr(main_sub, 7, 0, message);

    sprintf(message, "%s%u", S_HWREV, HardwareRevision);
    mvwaddstr(main_sub, 8, 0, message);

    wmove(main_sub, 9, 0);
    for (i = 0; ++i < COLS - MCOLS; waddch(main_sub, ACS_BSBS));

    if (st_len == 160) {
	sprintf(message, "%s%s", S_COUNTRYC, CountryCode);
	mvwaddstr(main_sub, 10, 0, message);

	sprintf(message, "%s%02u (%u MHz)", S_DEFCHAN, Channel,
	    2407 + 5 * Channel);
	mvwaddstr(main_sub, 11, 0, message);

	i = 1;
	while(ChannelInformation >> i) i++;
	sprintf(message, "%s%i", S_CHANNELS, i);
	mvwaddstr(main_sub, 12, 0, message);

	sprintf(message, "%s%u", S_TXPOWER,
	    TxPower[regdom_types[rd_idx].first_ch] & 0xFF);
	mvwaddstr(main_sub, 13, 0, message);
#if 0 /* DEBUG */
	wmove(main_sub, 18, 0);
	for (i = 0; i < 14; i++) {
	    sprintf(message, "%-3u ", TxPower[i] & 0xFF);
	    waddstr(main_sub, message);
	}
#endif /* DEBUG */

	wmove(main_sub, 14, 0);
	for (i = 0; ++i < COLS - MCOLS; waddch(main_sub, ACS_BSBS));

	sprintf(message, "System control OID part [%u elements]: ", sysOIDSize);
	mvwaddstr(main_sub, 15, 0, message);
	i = 0;
	while (i < (int)sysOIDSize) {
	    sprintf(message, ".%u", sysOID[i++]);
	    waddstr(main_sub, message);
	}

	sprintf(message, "USB Vendor ID: 0x%04X", (PID_VID[0] & 0xFF) |
	    ((PID_VID[1] & 0xFF) << 8));
	mvwaddstr(main_sub, 16, 0, message);

	sprintf(message, "USB Product ID: 0x%04X", (PID_VID[2] & 0xFF) |
	    ((PID_VID[3] & 0xFF) << 8));
	mvwaddstr(main_sub, 17, 0, message);

    }
    wrefresh(main_sub);

    print_help(OEMSET_HELP);

    while (1) {
	c = getch();
	switch (c) {
	    case 'm':
	    case 'M':
		get_mac(MacAddress, 1, strlen(S_MAC));
		clear_main_new(2, 3);
		sprintf(message, "%s%s", V_OUI, oui2manufacturer(MacAddress));
		mvwaddstr(main_sub, 2, 0, message);
		wrefresh(main_sub);
		continue;
	    case 'd':
	    case 'D':
		for (i = 0; regdom_types[i].code; i++);
		regdoms = malloc(i * sizeof(char *));

		for (i = 0; regdom_types[i].code; i++) {
		    sprintf(message, "[%3u] %s", regdom_types[i].code,
			regdom_types[i].desc);
		    regdoms[i] = (char *)
			malloc(strlen(message) + 1);
		    strcpy(regdoms[i], message);
		}

		rd_idx = menu_choose(3, strlen(S_REGDOM), regdoms, i);
		clear_main_new(3, 4);
		RegulatoryDomain = regdom_types[rd_idx].code;
		sprintf(message, "%s [%d]", regdom_types[rd_idx].desc,
		    RegulatoryDomain);
		print_menusel(3, 0, S_REGDOM, message);

		while (i-- != 0)
		    free(regdoms[i]);

		free(regdoms);

		if (st_len == 160) {
		    Channel = regdom_types[rd_idx].first_ch;
		    sprintf(message, "%02u (%u MHz)", Channel,
			2407 + 5 * Channel);
		    print_menusel(11, 0, S_DEFCHAN, message);
		}
		continue;
	    case 'e':
	    case 'E':
		get_value(OEMName, 4, strlen(S_OEMNAME), -32, ANY_STRING,
		    0, 0, NULL);
		continue;
	    case 'i':
	    case 'I':
		get_value(message, 5, strlen(S_OEMID), 11, INT_STRING,
		    0, 0xFFFFFFFF, OEMSET_HELP);
		OEMID = atoi(message);
		continue;
	    case 'n':
	    case 'N':
		get_value(ProductName, 6, strlen(S_PRODNAME), -32, ANY_STRING,
		    0, 0, NULL);
		continue;
	    case 't':
	    case 'T':
		get_value(message, 7, strlen(S_PRODTYPE), 11, INT_STRING,
		    0, 0xFFFFFFFF, OEMSET_HELP);
		ProductType = atoi(message);
		continue;
	    case 'h':
	    case 'H':
		get_value(message, 8, strlen(S_HWREV), 11,
		    INT_STRING, 0, 0xFFFFFFFF, OEMSET_HELP);
		HardwareRevision = atoi(message);
		continue;
	    case 'o':
	    case 'O':
		if (st_len == 92 || st_len == 128)
		    continue;

		get_value(CountryCode, 10, strlen(S_COUNTRYC), 3, ANY_STRING,
		    0, 0, NULL);
		continue;
	    case 'c':
	    case 'C':
		if (st_len == 92 || st_len == 128)
		    continue;

		Channel = menu_choose(11 - regdom_types[rd_idx].chans / 2,
		strlen(S_DEFCHAN) - 2,
#ifndef NO_REG_DOMAIN
                channels + regdom_types[rd_idx].first_ch - 1,
                regdom_types[rd_idx].chans) + regdom_types[rd_idx].first_ch;
#else
                channels, 14) + 1;
#endif
		sprintf(message, "%02u (%u MHz)", Channel, 2407 + 5 * Channel);
		print_menusel(11, 0, S_DEFCHAN, message);
		continue;
	    case 'a':
	    case 'A':
		if (st_len == 92 || st_len == 128)
		    continue;

		get_value(message, 12, strlen(S_CHANNELS), 3, INT_STRING, 1, 14,
		    OEMSET_HELP);
		memset((void *)TxPower + 1, 0, 13);
		for(i = 1; i < atoi(message); i++)
		    TxPower[i] = TxPower[0];

		i = 1 << (atoi(message) - 1);
		ChannelInformation = i | (i - 1);
		continue;
	    case 'p':
	    case 'P':
		if (st_len == 92 || st_len == 128)
		    continue;

		get_value(message, 13, strlen(S_TXPOWER), 4, INT_STRING, 0, 255,
		    OEMSET_HELP);
		c = atoi(message) & 0xFF;

		memset((void *)TxPower, 0, 14);
		i = 0;
		while(ChannelInformation >> i) {
		    TxPower[i] = c;
		    i++;
		}
/*
		memset((void *)TxPower, 0, 14);
		for (i = regdom_types[rd_idx].first_ch; i <
		regdom_types[rd_idx].first_ch + regdom_types[rd_idx].chans; i++)
		   TxPower[i - 1] = c;
*/
		continue;
	    case 'q':
	    case 'Q':
		goto quit;
	    case 'w':
	    case 'W':
		varbinds[0].oid = sysDeviceInfo;
		varbinds[0].len_oid = sizeof(sysDeviceInfo);
		varbinds[0].type = STRING_VALUE;

		if (st_len == 92 || st_len == 128) { /* SHORTst */
		    str128.RegulatoryDomain	= RegulatoryDomain;
		    str128.ProductType		= ProductType;
		    str128.OEMID		= OEMID;
		    str128.HardwareRevision	= HardwareRevision;
		    varbinds[0].value = (char *) &str128;
		    varbinds[0].len_val =
			sizeof(struct sysDeviceInfo_128);
		} else { /* st_len == 160 => LONGst */
		    str160.Channel		= Channel;
		    str160.RegulatoryDomain	= RegulatoryDomain;
		    str160.ProductType	= ProductType;
		    str160.OEMID		= OEMID;
		    str160.HardwareRevision	= HardwareRevision;
		    str160.ChannelInformation	= ChannelInformation;
		    varbinds[0].value = (char *) &str160;
		    /* first 92 bytes of struct sysDeviceInfo_160 here */
		    varbinds[0].len_val = 92;
		}

		print_help(WAIT_SET);
		if (snmp(varbinds, 1, SET) <= 0) {
		    print_helperr(ERR_SET);
		    goto exit;
		}

		if (st_len == 160) {
		    varbinds[0].oid = sysDeviceMoreInfo;
		    varbinds[0].len_oid = sizeof(sysDeviceMoreInfo);
		    varbinds[0].type = STRING_VALUE;
		    /* last 32 bytes of struct sysDeviceInfo_160 here */
		    varbinds[0].value = (char *) &str160.CountryCode;
		    varbinds[0].len_val = 32;
		    if (snmp(varbinds, 1, SET) <= 0) {
			print_helperr(ERR_SET);
			goto exit;
		    }
		}

		wbkgd(main_sub, A_NORMAL);
		wrefresh(main_sub);
		print_help(DONE_SET);
		goto exit;
	}
    }

  exit:
    getch();
  quit:
    print_top(NULL, NULL);
    clear_main(0);
}

