/*
    Copyright 2007,2008,2009 Luigi Auriemma

    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

    http://www.gnu.org/licenses/gpl-2.0.txt
*/

#define TYPE_8BIT       1
#define TYPE_16BIT      2
#define TYPE_32BIT      4
#define TYPE_64BIT      8
#define TYPE_FLOAT      16
#define TYPE_DOUBLE     32
#define TYPE_CRC        64
#define TYPE_FORCE_HEX  128
#define TYPE_AND        256
#define TYPE_NOBIG      512



#define ENDIAN_LITTLE   0
#define ENDIAN_BIG      1



enum {
    CMD_TITLE,
    CMD_TYPE,
    CMD_DATA,
    CMD_NONE = -1
};



u64     current_type;
u8      *current_title;



int delimit(u8 *data) {
    u8      *p;

    for(p = data; *p && (*p != '\n') && (*p != '\r'); p++);
    *p = 0;
    return(p - data);
}



int lowstr(u8 *data) {
    u8      *p;

    for(p = data; *p; p++) {
        *p = tolower(*p);
    }
    return(p - data);
}



u64 readbase(u8 *data, int size, int *ret_len) {
    static const u8 table[] = "0123456789abcdef";
    u64     num;
    int     sig = 0;
    u8      c,
            *p,
            *start;

    if(ret_len) *ret_len = 0;
    start = data;
    if(!data) return(0);
    if(!*data) return(0);

    if(*data == '-') {  // useless in calcc but can useful in other programs
        sig = 1;
        data++;
    }
    if((strlen(data) > 2) && (data[0] == '0') && (data[1] == 'x')) {
        size = 16;      // hex
        data += 2;
    }
    if((size == 10) && (data[0] == '0')) {
        size = 8;       // octal
        data++;
    }
    for(num = 0; *data; data++) {
        c = tolower(*data); // needed
        p = memchr(table, c, size);
        if(!p) break;
        num = (num * size) + (p - table);
    }
    if(sig) num = -num;
    if(ret_len) *ret_len = data - start;
    return(num);
}



u64 get_fmt_char(u8 **data) {
    u64     num = 0;
    int     len;
    u8      *str;

    str = *data;
    if(!str || !str[0]) {
        *data = NULL;
        return(0);
    }
    if(str[0] == '\\') {    // \n and so on
        len = 0;
        switch(str[1]) {
            case 0:    num = 0;    break;
            case '0':  num = '\0'; break;
            case 'a':  num = '\a'; break;
            case 'b':  num = '\b'; break;
            case 'e':  num = '\e'; break;
            case 'f':  num = '\f'; break;
            case 'n':  num = '\n'; break;
            case 'r':  num = '\r'; break;
            case 't':  num = '\t'; break;
            case 'v':  num = '\v'; break;
            case '\"': num = '\"'; break;
            case '\'': num = '\''; break;
            case '\\': num = '\\'; break;
            case '?':  num = '\?'; break;
            case '.':  num = '.';  break;
            case 'x':  num = readbase(str + 2, 16, &len);   break;  // hex
            default:   num = readbase(str + 1,  8, &len);   break;  // auto
        }
        len += 2;
    } else {
        len = 1;
        num = str[0];       // 'a'
    }

    str += len;
    if(!str[0]) {
        *data = NULL;
    } else {
        *data = str;
    }
    return(num);
}



int check_num_type(u8 *data) {
    int     c,
            ret = 0;
    u8      *p;

    for(p = data; (c = *p); p++) {
        if((c >= '0') && (c <= '9')) {
            // ret = 0;
        } else if((c >= 'a') && (c <= 'f')) {
            ret = TYPE_FORCE_HEX;
        } else if(c == '.') {
            ret = TYPE_FLOAT;
            break;
        }
    }
    return(ret);
}



u64 get_num(u8 *data) {
    float   numf;
    double  numlf;
    int     chk;
    u64     num;
    u32     tmp32;
    u8      *p;

    if(!data || !data[0]) return(0);

    num = 0;
    if(data[0] == '\'') {
        p = data + 1;
        num = get_fmt_char(&p);
    } else {
        lowstr(data);

        if(data[0] == '_') data++;
        chk = check_num_type(data);

        if(!strcmp(data, "int_min")) {                                  // INT_MIN
            num = (u64)0x80000000;
        } else if(!strcmp(data, "int_max")) {                           // INT_MAX
            num = (u64)0x7fffffff;
        } else if(!strcmp(data, "i64_min")) {                           // I64_MIN
            num = (u64)0x8000000000000000ULL;
        } else if(!strcmp(data, "i64_max")) {                           // I64_MAX
            num = (u64)0x7fffffffffffffffULL;
        } else if(current_type & TYPE_DOUBLE) {                         // DOUBLE
            //if(chk != TYPE_FLOAT) printf("- %s\n  a double without dot???\n", current_title);
            numlf = atof(data);
            memcpy(&num, &numlf, sizeof(numlf));
        } else if(strchr(data, '.') || (current_type & TYPE_FLOAT)) {   // FLOAT
            //if(chk != TYPE_FLOAT) printf("- %s\n  a float without dot???\n", current_title);
            numf = atof(data);
            memcpy(&tmp32, &numf, 4);
            num = tmp32;
        } else if(strstr(data, "0x") || strchr(data, '$') || strchr(data, 'h') || (current_type & TYPE_FORCE_HEX)){
            if(chk == TYPE_FLOAT) goto error;                           // HEX
            num = readbase(data, 16, NULL);
        } else {                                                        // DECIMAL
            if((chk == TYPE_FORCE_HEX) || (chk == TYPE_FLOAT)) goto error;
            num = readbase(data, 10, NULL);
        }
    }

    return(num);

error:
    printf("\n"
        "Error: %s\n"
        "       the number \"%s\" doesn't match the type specified\n",
        current_title,
        data);
    free_sign();
    exit(1);
}



u8 *get_cfg_cmd(u8 *line, int *cmdnum) {
    int     i,
            cmdret;
    u8      *cmd,
            *p,
            *l;
    static const u8 *command[] = {
            "TITLE",
            "TYPE",
            "DATA",
            NULL };

    cmdret  = CMD_NONE;
    *cmdnum = CMD_NONE;

    l = line + delimit(line);

    for(p = line; *p; p++) {        // clear start
        if((*p != ' ') && (*p != '\t')) break;
    }
    if(!*p) return(NULL);

    cmd = p;                        // cmd

    for(l--; l > p; l--) {          // clear end
        if(*l > ' ') break;
    }
    *(l + 1) = 0;

    if((*cmd == '=') || (*cmd == '#') || (*cmd == '/') || (*cmd == ';')) return(NULL);

    for(p = cmd; *p > ' '; p++);    // find where the command ends

    for(i = 0; command[i]; i++) {
        if(!memcmp(cmd, command[i], p - cmd)) {
            cmdret = i;
            break;
        }
    }

    if(cmdret != CMD_NONE) {        // skip the spaces between the comamnd and the instructions
        for(; *p; p++) {
            if((*p != ' ') && (*p != '\t')) break;
        }
        cmd = p;
    }

    // do not enable this or will not work!
    // if((*cmd == '=') || (*cmd == '#') || (*cmd == '/') || (*cmd == ';')) return("");

    *cmdnum = cmdret;
    return(cmd);
}



    /* here we catch each line (till line feed) */
    /* returns a pointer to the next line       */
u8 *get_line(u8 *data) {
    u8      *p;

    for(p = data; *p && (*p != '\n') && (*p != '\r'); p++);
    if(!*p) return(NULL);
    *p = 0;
    for(p++; *p && ((*p == '\n') || (*p == '\r')); p++);
    if(!*p) return(NULL);
    return(p);
}



    /* here we catch each element of the line */
    /* returns a pointer to the next element  */
u8 *get_element(u8 **data, int *isastring) {
    u8      *p;

    p = *data;

    if(p[0] == '\'') {
        for(p++; *p; p++) {
            if(p[0] == '\'') {
                p++;
                break;
            }
        }
    } else if((p[0] == '/') && (p[1] == '*')) {    // /* comment */
        for(p += 2; *p; p++) {
            if((p[0] == '*') && (p[1] == '/')) {
                p += 2;
                break;
            }
        }
    } else if(*p == '"') {                  // string
        if(isastring) *isastring = 1;
        p++;
        for(*data = p; *p && (*p != '\"'); p++) {
            if(*p == '\\') p++;
            if(!*p) break;
        }
    } else {
        if(isastring) *isastring = 0;   // the following are delimiters
        while(*p && (*p != '\t') && (*p != ' ') && (*p != ',') && (*p != '{') && (*p != '}') && (*p != '(') && (*p != ')') && (*p != '\\')) {
            if((*p == '%') || (*p == '*')) {    // + and - are ok, it's not easy to make distinction between inline operations and negative/positive numbers of exponential floats
                fprintf(stderr, "\nError: found some invalid chars in the list\n");
                exit(1);
            }
            p++;
        }
    }

    if(!*p) return(NULL);                   // end of line
    *p = 0;

    for(p++; *p && ((*p == '\t') || (*p == ' ')); p++);
    if(!*p) return(NULL);                   // start of next line
    return(p);
}



void cfg_title(u8 *line) {
    if(current_title) free(current_title);
    current_title = strdup(line);
}



void cfg_type(u8 *line) {
    u8      *next,
            *sc,
            *scn;

    current_type = 0;

    next = NULL;
    do {
        next = get_line(line);

        sc  = line;
        scn = NULL;
        do {
            scn = get_element(&sc, NULL);

            if((sc[0] == '#') || (sc[0] == '/') || (sc[0] == ';')) break; // comments, ';' is used also at the end of the C structures

            lowstr(sc);

#define C(X)    !strcmp(sc, X)
#define S(X)    strstr(sc, X)
            if(C("unsigned")) continue;
            if(!memcmp(sc, "u_", 2)) sc += 2;
            if(sc[0] == 'u') sc++;

            if(S("int8")  || C("8")  || S("char"))              current_type |= TYPE_8BIT;
            if(S("int16") || C("16") || S("short"))             current_type |= TYPE_16BIT;
            if(S("int32") || C("32") || C("int") || S("long"))  current_type |= TYPE_32BIT;
            if(S("int64") || C("64"))                           current_type |= TYPE_64BIT;
            if(C("float"))                                      current_type |= TYPE_FLOAT;
            if(C("crc")   || C("checksum"))                     current_type |= TYPE_CRC;
            if(C("hex")   || C("forcehex"))                     current_type |= TYPE_FORCE_HEX;
            if(C("and")   || C("&&"))                           current_type |= TYPE_AND;
            if(C("nobig"))                                      current_type |= TYPE_NOBIG;
#undef C
#undef S

            sc = scn;
        } while(scn);

        line = next;
    } while(next);
}



u8 *cfg_add_element(u8 *op, int *oplen, u64 num, int size, int endian) {
    int     len = *oplen;

    if(!alt_endian && (endian == ENDIAN_BIG)) return(op);
    if((size == 8) && (endian == ENDIAN_BIG)) return(op);

    if((int64_t)num >= 0) {
        if((size == 8)  && (num > 0xff))        goto error;
        if((size == 16) && (num > 0xffff))      goto error;
        if((size == 32) && (num > 0xffffffff))  goto error;
    }

    len += size >> 3;
    op = realloc(op, len);
    if(!op) std_err();

    if(size == 8) {
        op[len - 1] = num;

    } else if(size == 16) {
        if(endian == ENDIAN_LITTLE) {
            op[len - 2] = (num      );
            op[len - 1] = (num >>  8);
        } else {
            op[len - 2] = (num >>  8);
            op[len - 1] = (num      );
        }

    } else if(size == 32) {
        if(endian == ENDIAN_LITTLE) {
            op[len - 4] = (num      );
            op[len - 3] = (num >>  8);
            op[len - 2] = (num >> 16);
            op[len - 1] = (num >> 24);
        } else {
            op[len - 4] = (num >> 24);
            op[len - 3] = (num >> 16);
            op[len - 2] = (num >>  8);
            op[len - 1] = (num      );
        }

    } else if(size == 64) {
        if(endian == ENDIAN_LITTLE) {
            op[len - 8] = (num      );
            op[len - 7] = (num >>  8);
            op[len - 6] = (num >> 16);
            op[len - 5] = (num >> 24);
            op[len - 4] = (num >> 32);
            op[len - 3] = (num >> 40);
            op[len - 2] = (num >> 48);
            op[len - 1] = (num >> 56);
        } else {
            op[len - 8] = (num >> 56);
            op[len - 7] = (num >> 48);
            op[len - 6] = (num >> 40);
            op[len - 5] = (num >> 32);
            op[len - 4] = (num >> 24);
            op[len - 3] = (num >> 16);
            op[len - 2] = (num >>  8);
            op[len - 1] = (num      );
        }
    }

    *oplen = len;
    return(op);

error:
    printf("\n"
        "Error: %u) %s\n"
        "       the number 0x%08x%08x is bigger than %d bits\n"
        "       check your signature file, probably you must increate the TYPE size\n",
        signs, current_title,
        (u32)((num >> 32) & 0xffffffff), (u32)(num & 0xffffffff), size);
    free_sign();
    exit(1);
}    



void add_sign(u8 *type, u8 *endian, u8 *data, int datasize, int bits) {
    static int  signs_blocks    = 0; // limits a realloc massacre
    int     len;

    if(!datasize) return;
    if(!*type) endian = "";
    if(signs >= signs_blocks) {
        signs_blocks += 8000;   // a big amount, I doubt that signsrch.sig will reach this number of entries
        sign = realloc(sign, sizeof(sign_t *) * signs_blocks);
        if(!sign) std_err();
    }
    sign[signs]        = malloc(sizeof(sign_t));
    if(!sign[signs]) std_err();
    sign[signs]->title = malloc(strlen(current_title) + strlen(type) + strlen(endian) + 10 + 5 + 1);
    len = sprintf(sign[signs]->title, "%s [%s.%s.%u%s]",
        current_title, type, endian, datasize, (current_type & TYPE_AND) ? "&" : "");
    sign[signs]->data  = data;
    sign[signs]->size  = datasize;
    sign[signs]->and   = 0;
    if(current_type & TYPE_AND) sign[signs]->and = bits;

    sign_alloclen +=
        + sizeof(sign_t *)
        + sizeof(sign_t)
        + len
        + datasize;
    signs++;
}



#define BITMASK(SIZE)   ((u64)1 << (u64)(SIZE))



u64 reflect(u64 v, int b) {
    u64     t;
    int     i;

    t = v;
    for(i = 0; i < b; i++) {
        if(t & (u64)1) {
            v |= BITMASK((b - 1) - (u64)i);
        } else {
            v &= (BITMASK((b - 1) ^ (u64)0xffffffffffffffffLL) - (u64)i);
        }
        t >>= (u64)1;
    }
    return(v);
}



u64 widmask(int size) {
    return((((u64)1 << (u64)(size - 1)) - (u64)1) << (u64)1) | (u64)1;
}



u64 cm_tab(int inbyte, u64 poly, int size, int rever) {
    u64     r,
            topbit;
    int     i;

    topbit = BITMASK(size - 1);

    if(rever) inbyte = reflect(inbyte, 8);

    r = (u64)inbyte << (u64)(size - 8);

    for(i = 0; i < 8; i++) {
        if(r & topbit) {
            r = (r << (u64)1) ^ poly;
        } else {
            r <<= (u64)1;
        }
    }

    if(rever) r = reflect(r, size);

    return(r & widmask(size));
}



u8 *make_crc(u8 *op, int *oplen, u64 poly, int size, int endian, int rever) {
    u64     num;
    int     i,
            len = *oplen;

    for(i = 0; i < 256; i++) {
        num = cm_tab(i, poly, size, rever);
        op = cfg_add_element(op, &len, num, size, endian);
    }

    *oplen = len;
    return(op);
}



void cfg_data(u8 *line) {
    int     opi8len   = 0,
            opi16len  = 0,
            opi32len  = 0,
            opi64len  = 0,
            opifltlen = 0,
            opidbllen = 0;
    u8      *opi8     = NULL,
            *opi16    = NULL,
            *opi32    = NULL,
            *opi64    = NULL,
            *opiflt   = NULL,
            *opidbl   = NULL;

    int     opb8len   = 0,
            opb16len  = 0,
            opb32len  = 0,
            opb64len  = 0,
            opbfltlen = 0,
            opbdbllen = 0;
    u8      *opb8     = NULL,   // NEVER used
            *opb16    = NULL,
            *opb32    = NULL,
            *opb64    = NULL,
            *opbflt   = NULL,
            *opbdbl   = NULL;

    int     opicrclen = 0,
            opbcrclen = 0;
    u8      *opicrc   = NULL,
            *opbcrc   = NULL;

    int     opstrlen  = 0;
    u8      *opstr    = NULL;

    u64     num;
    int     isastring = 0;
    u8      *next,
            *sc,
            *scn,
            *p;

    if(!current_type) current_type |= TYPE_8BIT;

    next = NULL;
    do {
        next = get_line(line);

        sc  = line;
        scn = NULL;
        do {
            scn = get_element(&sc, &isastring);

            if((sc[0] == '/') && (sc[1] == '*')) goto scn_continue; // don't touch
            if((sc[0] == '#') || (sc[0] == '/') || (sc[0] == ';')) break; // comments, ';' is used also at the end of the C structures
            if(!sc[0]) goto scn_continue;

            if(isastring) {
                for(p = sc; p;) {
                    num = get_fmt_char(&p);
                    opstr = cfg_add_element(opstr, &opstrlen, num, 8, ENDIAN_LITTLE);
                }
                goto scn_continue;
            }

            num = get_num(sc);

            if(current_type & TYPE_CRC) {
                    /* ONLY ONE CRC AT TIME IS ALLOWED */

#define DOIT(TYPENAME, BITS, TYPE)  \
                if(current_type & TYPE_##TYPENAME) {                                        \
                    opicrc = make_crc(NULL, &opicrclen, num, BITS, ENDIAN_LITTLE, 1);       \
                    add_sign(TYPE, "le rev", opicrc, opicrclen, BITS);                      \
                    if((num != 1) && (current_type & TYPE_NOBIG)) {                         \
                        opicrclen = 0;                                                      \
                        opicrc = make_crc(NULL, &opicrclen, num, BITS, ENDIAN_LITTLE, 0);   \
                        add_sign(TYPE, "le", opicrc, opicrclen, BITS);                      \
                    }                                                                       \
                    if(BITS > 8) {                                                          \
                        opbcrc = make_crc(NULL, &opbcrclen, num, BITS, ENDIAN_BIG, 1);      \
                        add_sign(TYPE, "be rev", opbcrc, opbcrclen, BITS);                  \
                        if(current_type & TYPE_NOBIG) {                                     \
                            opbcrclen = 0;                                                  \
                            opbcrc = make_crc(NULL, &opbcrclen, num, BITS, ENDIAN_BIG, 0);  \
                            add_sign(TYPE, "be", opbcrc, opbcrclen, BITS);                  \
                        }                                                                   \
                    }                                                                       \
                }

                DOIT(8BIT,   8,   "")
                DOIT(16BIT,  16,  "16")
                DOIT(32BIT,  32,  "32")
                DOIT(64BIT,  64,  "64")

#undef DOIT

                return;
            }

#define DOIT(TYPENAME, NAME, BITS)  \
            if(current_type & TYPE_##TYPENAME) {  \
                opi##NAME = cfg_add_element(opi##NAME, &opi##NAME##len, num, BITS, ENDIAN_LITTLE);  \
                opb##NAME = cfg_add_element(opb##NAME, &opb##NAME##len, num, BITS, ENDIAN_BIG);     \
            }

            DOIT(8BIT,   8,   8)
            DOIT(16BIT,  16,  16)
            DOIT(32BIT,  32,  32)
            DOIT(64BIT,  64,  64)
            DOIT(FLOAT,  flt, 32)

                /* stupid and lame work-around for double and float */
                /* but it works 8-) */
            if(current_type & TYPE_FLOAT) {     // if float = do double too
                current_type |= TYPE_DOUBLE;    // enable double
                num = get_num(sc);              // re-read the number
                DOIT(DOUBLE, dbl, 64)           // add it
                current_type ^= TYPE_DOUBLE;    // disable double
            }

#undef DOIT

scn_continue:
            sc = scn;
        } while(scn);

        line = next;
    } while(next);

#define DOIT(NAME, BITS, TYPE)    \
    if(opi##NAME) add_sign(TYPE, "le", opi##NAME, opi##NAME##len, BITS);    \
    if(current_type & TYPE_NOBIG) {                                         \
        free(opb##NAME);                                                    \
        opb##NAME = NULL;                                                   \
    }                                                                       \
    if(opb##NAME) {                                                         \
        if(opi##NAME) { /* remove duplicates! */                            \
            if(!memcmp(opi##NAME, opb##NAME, opb##NAME##len)) {             \
                free(opb##NAME);                                            \
            } else {                                                        \
                add_sign(TYPE, "be", opb##NAME, opb##NAME##len, BITS);      \
            }                                                               \
        }                                                                   \
    }

    DOIT(8,     8,      "")
    DOIT(16,    16,     "16")
    DOIT(32,    32,     "32")
    DOIT(64,    64,     "64")
    DOIT(flt,   32,     "float")
    DOIT(dbl,   64,     "double") 
    if(opstr) add_sign("", "", opstr, opstrlen, 8);

#undef DOIT
}



void cfg_cmd(int cmdnum, u8 *line) {
    switch(cmdnum) {
        case CMD_TITLE: cfg_title(line);    break;
        case CMD_TYPE:  cfg_type(line);     break;
        case CMD_DATA:  cfg_data(line);     break;
        default:                            break;
    }
}



void read_cfg(u8 *filename) {
    FILE    *fd;
    int     len,
            currlen,
            bufflen,
            oldnum,
            cmdnum,
            tmp;
    u8      line[256],
            *buff,
            *buff_limit,
            *data,
            *ins;

    printf("- open file %s\n", filename);
    fd = fopen(filename, "rb");
    if(!fd) std_err();

    bufflen    = 256;
    buff       = malloc(bufflen);
    if(!buff) std_err();
    data       = buff;
    buff_limit = buff + bufflen;
    buff[0]    = 0;
    line[0]    = 0;
    oldnum     = CMD_NONE;

    while(fgets(line, sizeof(line), fd)) {
        ins = get_cfg_cmd(line, &cmdnum);
        if(!ins) continue;

        if(oldnum == CMD_NONE) oldnum = cmdnum;
        if(cmdnum == CMD_NONE) cmdnum = oldnum;
        if(cmdnum != oldnum) {
            tmp    = cmdnum;
            cmdnum = oldnum;
            oldnum = tmp;

            cfg_cmd(cmdnum, buff);

            data = buff;
        }

        len = strlen(ins);  // allocation
        if((data + len) >= buff_limit) {
            currlen    = data - buff;
            bufflen    = currlen + 1 + len + 1; // 1 for \n and 1 for the final NULL byte
            buff       = realloc(buff, bufflen);
            if(!buff) std_err();
            data       = buff + currlen;
            buff_limit = buff + bufflen;
        }

        if(data > buff) data += sprintf(data, "\n");
        data += sprintf(data, "%s", ins);
        line[0] = 0;
    }
        // the remaining line
    cmdnum = oldnum;
    if((cmdnum != CMD_NONE) && (data != buff)) cfg_cmd(cmdnum, buff);

    free(buff);
    fclose(fd);
}