/* ipcalc - IP address bitwise calculator
 * example:
 *
 * # ipcalc 210.154.33.106 and /28
 * 210.154.33.96
 * # ipcalc not /28
 * 0.0.0.15
 * # ipcalc 210.154.33.106 or not /28
 * 210.154.33.111
 */

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define IP_STRMAXSIZ sizeof("000.000.000.000")
typedef unsigned int IP;

enum token_types {
    TOK_IP,	/* 192.168.1.3, /24 */
    TOK_OR,	/* or */
    TOK_XOR,	/* xor */
    TOK_AND,	/* and */
    TOK_NOT,	/* not */
    TOK_LP,	/* [ */
    TOK_RP,	/* ] */
    TOK_END
};

enum output_type {
    OUTPUT_DOT,
    OUTPUT_HEX,
    OUTPUT_BIN
};

static struct {
    const char *name;
    int type;
} token_table[] = {
    {"or",	TOK_OR},
    {"|",	TOK_OR},
    {"xor",	TOK_XOR},
    {"^",	TOK_XOR},
    {"and",	TOK_AND},
    {"&",	TOK_AND},
    {"not",	TOK_NOT},
    {"~",	TOK_NOT},
    {"(",	TOK_LP},
    {"[",	TOK_LP},
    {"{",	TOK_LP},
    {")",	TOK_RP},
    {"]",	TOK_RP},
    {"}",	TOK_RP},
    {NULL,	-1}
};


static int token_type;
static IP token_value;
static int token_count;

static int main_argc;
static char **main_argv;

static void usage(void)
{
    fprintf(stderr, "ipcalc [-hDHB] expr ...\n");
    exit(1);
}

static int parse_bin_ip(const char *str, IP *addr)
{
    int i, b;

    *addr = 0;
    for(i = 0; i < 32; i++)
    {
	if(str[i] == '0')
	    b = 0;
	else if(str[i] == '1')
	    b = 1;
	else
	    return 0;
	*addr = (*addr << 1) | b;
    }
    return 1;
}

static int parse_ip(const char *str, IP *addr)
{
    int shift, octet, len;
    char buff[IP_STRMAXSIZ];
    char *s, *t;

    len = strlen(str);
    if(len == 32)
	return parse_bin_ip(str, addr);

    if(len > sizeof(buff))
	return 0;
    strcpy(buff, str);

    s = buff;
    shift = 24;
    *addr = 0;
    while(*s) {
	t = s;
	if(!isdigit(*t))
	    return 0;
	while(isdigit(*t))
	    t++;
	if(*t == '.')
	    *t++ = '\0';
	else if(*t)
	    return 0;
	if(shift < 0)
	    return 0;
	octet = atoi(s);
	if(octet < 0 || octet > 255)
	    return 0;
	*addr |= octet << shift;
	s = t;
	shift -= 8;
    }
    return 1;
}

static int parse_hex_ip(const char *str, IP *addr)
{
    int i, h;

    if(strlen(str) != 8)
	return 0;
    *addr = 0;
    for(i = 7; i >= 0; i--)
    {
	h = str[i];
	if('0' <= h && h <= '9')
	    h -= '0';
	else if('A' <= h && h <= 'F')
	    h -= 'A' - 10;
	else if('a' <= h && h <= 'f')
	    h -= 'a' - 10;
	else
	    return 0;
	*addr = *addr << 4 | h;
    }
    return 1;
}

static void nexttoken(void)
{
    char *tok;
    int i;

    if(token_count == main_argc)
    {
	token_type = TOK_END;
	return;
    }

    tok = main_argv[token_count++];

    if(tok[0] == '0' && tok[1] == 'x')
    {
	if(!parse_hex_ip(tok + 2, &token_value))
	    goto error;
	token_type = TOK_IP;
	return;
    }

    if('0' <= *tok && *tok <= '9')
    {
	if(!parse_ip(tok, &token_value))
	    goto error;
	token_type = TOK_IP;
	return;
    }

    if(*tok == '/')
    {
	int bits;

	if(!isdigit(tok[1]))
	    goto error;

	bits = atoi(tok + 1);
	if(bits < 0 || bits > 32)
	    goto error;
	bits = 32 - bits; /* convert host bits */
	token_type = TOK_IP;
	token_value = (~0u >> bits) << bits;
	return;
    }

    for(i = 0; token_table[i].name; i++)
    {
	if(strcmp(tok, token_table[i].name) == 0)
	{
	    token_type = token_table[i].type;
	    token_value = 0;
	    return;
	}
    }

  error:
    fprintf(stderr, "Unexpected token: %s\n", tok);
    exit(1);
    return; /* dummy */
}

static void start_parser(int argc, char **argv)
{
    main_argc = argc;
    main_argv = argv;
    token_count = 0;
}

static char *ip2str(IP addr, int type)
{
    static char buff[33];
    char *s;
    int shift;

    s = buff;
    switch(type)
    {
      case OUTPUT_HEX:
	sprintf(buff, "0x%08x", addr);
	break;

      case OUTPUT_BIN:
	for(shift = 31; shift >= 0; shift--)
	{
	    if((addr >> shift) & 1)
		*s++ = '1';
	    else
		*s++ = '0';
	}
	*s = '\0';
	break;

      case OUTPUT_DOT:
      default:
	for(shift = 24; shift >= 0; shift -= 8)
	{
	    sprintf(s, "%d", (addr >> shift) & 0xff);
	    s += strlen(s);
	    if(shift != 0)
		*s++ = '.';
	}
	*s = '\0';
    }
    return buff;
}

static IP expr(void);
static IP expr_or(void);
static IP expr_xor(void);
static IP expr_and(void);
static IP expr_not(void);
static IP expr_p(void);
static IP expr_atom(void);

static IP expr(void)
{
    IP value;

    nexttoken();
    value = expr_or();
    if(token_type != TOK_END)
    {
	fprintf(stderr, "Syntax error\n");
	exit(1);
    }
    return value;
}

static IP expr_or(void)
{
    IP value;

    value = expr_xor();
    if(token_type == TOK_OR)
    {
	nexttoken();
	value |= expr_or();
    }
    return value;
}

static IP expr_xor(void)
{
    IP value;

    value = expr_and();
    if(token_type == TOK_XOR)
    {
	nexttoken();
	value ^= expr_xor();
    }
    return value;
}

static IP expr_and(void)
{
    IP value;

    value = expr_not();
    if(token_type == TOK_AND)
    {
	nexttoken();
	value &= expr_and();
    }
    return value;
}

static IP expr_not(void)
{
    IP value;

    if(token_type == TOK_NOT)
    {
	nexttoken();
	value = ~expr_not();
    }
    else
	value = expr_p();
    return value;
}

static IP expr_p(void)
{
    IP value;

    if(token_type == TOK_LP)
    {
	nexttoken();
	value = expr_or();
	if(token_type != TOK_RP)
	{
	    fprintf(stderr, "Parenthesis miss match\n");
	    exit(1);
	}
	nexttoken();
    }
    else
	value = expr_atom();
    return value;
}

static IP expr_atom(void)
{
    IP value;

    switch(token_type)
    {
      case TOK_IP:
	value = token_value;
	nexttoken();
	return value;
    }
    fprintf(stderr, "Syntax error\n");
    exit(1);
    return 0; /* dummy */
}

int main(int argc, char **argv)
{
    IP addr;
    int output_type = OUTPUT_DOT;
    int i;

#ifdef sun
    extern char *optarg;
    extern int optind;
#endif /* sun */

    if(argc < 2)
	usage();

    while((i = getopt(argc, argv, "hHDB")) > 0)
    {
	switch(i)
	{
	  case 'D':
	    output_type = OUTPUT_DOT;
	    break;
	  case 'H':
	    output_type = OUTPUT_HEX;
	    break;
	  case 'B':
	    output_type = OUTPUT_BIN;
	    break;
	  case '?':
	  case 'h':
	    usage();
	}
    }

    if(optind >= argc)
	usage();

    start_parser(argc - optind, argv + optind);

    addr = expr();
    printf("%s\n", ip2str(addr, output_type));
    return 0;
}
