/*
 * wireless.c
 * Copyright (C) Aitor Cuadrado Zubizarreta <aitor@genuen.org>
 * 
 * This file is part of the Wireless Tools project
 *
 *      Jean II - HPLB '99 - HPL 99->07
 *  
 * released under the GPL license.
 *     Copyright (c) 1997-2007 Jean Tourrilhes <jt@hpl.hp.com>
 * 
 * simple-netaid 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 3 of the License, or
 * (at your option) any later version.
 * 
 * simple-netaid 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, see <http://www.gnu.org/licenses/>.
 * 
 * See the COPYING file. 
 */

/*
 * This tool can access various piece of information on the card
 * not part of iwconfig...
 * You need to link this code against "-liw". 
 */

#include <sys/time.h>
#include <iwlib.h>

#include "iwlist.h"

#define MAX_SCAN_BUFLEN (128 * 1024) /* 128 KB limit to prevent RAM exhaustion */

/****************************** TYPES ******************************/

/*
 * Scan state and meta-information, used to decode events...
 */
typedef struct iwscan_state {
    /* State */
    int	ap_num;		/* Access Point number 1->N */
    int	val_index;	/* Value in table 0->(N-1) */
    void *tbl;   /* Ahora el puntero a la tabla JSON vive aquí */
} iwscan_state;

/**************************** CONSTANTS ****************************/

#define IW_SCAN_HACK		0x8000

/*------------------------------------------------------------------*/
/*
 * Display an Ethernet Socket Address in readable format.
 */
static inline char *iw_saether_ntop(const struct sockaddr *sap, char* bufp)
{
    iw_ether_ntop((const struct ether_addr *) sap->sa_data, bufp);
    return bufp;
}

/***************************** SCANNING *****************************/
/*
 * This one behave quite differently from the others
 *
 * Note that we don't use the scanning capability of iwlib (functions
 * iw_process_scan() and iw_scan()). The main reason is that
 * iw_process_scan() return only a subset of the scan data to the caller,
 * for example custom elements and bitrates are ommited. Here, we
 * do the complete job...
 */

/*------------------------------------------------------------------*/
/*
 * Print one element from the scanning results
 */
static inline int print_scanning_token(struct stream_descr *stream,	 /* Stream of events */
                                       struct iw_event *event,	     /* Extracted token */
                                       struct iwscan_state *state,
                                       struct iw_range *iw_range,	 /* Range info */
                                       int has_range,
                                       struct blob_buf *buf)
{
    char buffer[128];	/* Temporary buffer */
  
    /* Now, let's decode the event */
    switch(event->cmd) {

    case SIOCGIWAP:
        /* 
         * SECURITY: If tbl is not NULL, it means that a previous cell 
         * was not closed correctly (due to lack of ESSID or another event).
         * We close it now so as not to corrupt the JSON struct.
         */
        if (state->tbl != NULL) {
            blobmsg_close_table(buf, state->tbl);
        }

        snprintf(buffer, sizeof(buffer), "Cell %02d", state->ap_num);
        state->tbl = blobmsg_open_table(buf, buffer);
      
        blobmsg_add_string(buf, "Address", iw_saether_ntop(&event->u.ap_addr, buffer));
        state->ap_num++;
        break;

    case SIOCGIWESSID:
        {
            char essid[4*IW_ESSID_MAX_SIZE+1];
            memset(essid, '\0', sizeof(essid));
        
            if ((event->u.essid.pointer) && (event->u.essid.length))
                iw_essid_escape(essid, event->u.essid.pointer, event->u.essid.length);

            if (event->u.essid.flags) {
                if((event->u.essid.flags & IW_ENCODE_INDEX) > 1) {
                    char final_essid[sizeof(essid) + 15];
                    snprintf(final_essid, sizeof(final_essid), "%s [%d]", 
                            essid, (event->u.essid.flags & IW_ENCODE_INDEX));
                    blobmsg_add_string(buf, "Essid", final_essid);
                } else {
                    blobmsg_add_string(buf, "Essid", essid);
                }
            } else {
                blobmsg_add_string(buf, "Essid", "off/any/hidden");
            }
        } 
        break;

    case SIOCGIWENCODE:
        {  
            unsigned char key[IW_ENCODING_TOKEN_MAX];
            int key_len = event->u.data.length;

            if (event->u.data.pointer) {
                if (key_len > (int)IW_ENCODING_TOKEN_MAX)
                    key_len = IW_ENCODING_TOKEN_MAX;
                if (key_len < 0)
                    key_len = 0;
                memcpy(key, event->u.data.pointer, key_len);
            } else {
                event->u.data.flags |= IW_ENCODE_NOKEY;
                key_len = 0;
            }

            if (event->u.data.flags & IW_ENCODE_DISABLED) {
                blobmsg_add_string(buf, "Encryption key", "off");
            } else {
                char key_buf[128];
                char info_buf[256];
            
                iw_print_key(key_buf, sizeof(key_buf), key,
                            key_len, event->u.data.flags);
            
                snprintf(info_buf, sizeof(info_buf), "%s%s%s%s",
                    key_buf,
                    ((event->u.data.flags & IW_ENCODE_INDEX) > 1) ? " [index]" : "",
                    (event->u.data.flags & IW_ENCODE_RESTRICTED) ? " Security mode:restricted" : "",
                    (event->u.data.flags & IW_ENCODE_OPEN) ? " Security mode:open" : "");

                blobmsg_add_string(buf, "Encryption key", info_buf);
            }
        }
        break;
      
    case IWEVQUAL:
        if (has_range && (((&event->u.qual)->level != 0) ||
                ((&event->u.qual)->updated & (IW_QUAL_DBM | IW_QUAL_RCPI)))) {
        
            /* Deal with quality : always a relative value */
            if (!((&event->u.qual)->updated & IW_QUAL_QUAL_INVALID)) {
                snprintf(buffer, sizeof(buffer), "%d/%d",
                        (&event->u.qual)->qual, iw_range->max_qual.qual);
                blobmsg_add_string(buf, "Quality", buffer);
            }
        }
        if (has_range) {
            snprintf(buffer, sizeof(buffer), "%d dBm", (event->u.qual.level >= 64) ? 
                    event->u.qual.level - 0x100 : event->u.qual.level);
            blobmsg_add_string(buf, "Signal", buffer);
        }
        break;

    default:
        break;
   }	/* switch(event->cmd) */
   
   return (state->ap_num - 1);
}

/*------------------------------------------------------------------*/
/*
 * Perform a scanning on one device
 */
static int print_scanning_info(int skfd, const char *ifname, struct blob_buf *buf)
{
    struct iwreq wrq;
    struct iw_scan_req scanopt;
    unsigned char *buffer = NULL;
    int buflen = IW_SCAN_MAX_DATA;
    struct iw_range range;
    int has_range;
    struct timeval tv;
    struct iwscan_state state = { .ap_num = 1, .tbl = NULL };
    
    has_range = (iw_get_range_info(skfd, ifname, &range) >= 0);

    if ((!has_range) || (range.we_version_compiled < 14))
        return(-1);

    /* Initial timeout for hardware to fill the buffer */
    tv.tv_sec = 0;
    tv.tv_usec = 250000; /* 250ms is the recommended standard */

    memset(&scanopt, 0, sizeof(scanopt));
    wrq.u.data.pointer = (caddr_t) &scanopt;
    wrq.u.data.length = sizeof(scanopt);
    wrq.u.data.flags = 0;
        
    if(iw_set_ext(skfd, ifname, SIOCSIWSCAN, &wrq) < 0) {
        if(errno != EPERM) return -1;
        /* If there is no permission, we read whatever is there without waiting. */
        tv.tv_usec = 0; 
    }

    while(1) {
        fd_set rfds;
        FD_ZERO(&rfds);
        int ret = select(0, &rfds, NULL, NULL, &tv);

        

        if (ret < 0) {
            if(errno == EAGAIN || errno == EINTR) continue;
            goto error_out;
        }

        if (ret == 0) {
            unsigned char *newbuf;

realloc_buffer:
            if (buflen > MAX_SCAN_BUFLEN)
                goto error_out;

            newbuf = realloc(buffer, buflen);
            if (!newbuf)
                goto error_out;
    
            buffer = newbuf;
            wrq.u.data.pointer = buffer;
            wrq.u.data.flags = 0;
            wrq.u.data.length = buflen;

            if (iw_get_ext(skfd, ifname, SIOCGIWSCAN, &wrq) < 0) {
                if (errno == E2BIG) {
                    buflen *= 2; 
                    goto realloc_buffer;
                }
                
                if (errno == EAGAIN) {
                    tv.tv_sec = 0;
                    tv.tv_usec = 500000;
                    continue; 
                }
                goto error_out;
            }

            /* --- Data processing --- */
            struct stream_descr stream;
            struct iw_event event;
            
            iw_init_event_stream(&stream, (char *) buffer, wrq.u.data.length);
            
            while (iw_extract_event_stream(&stream, &event, range.we_version_compiled) > 0) {
                print_scanning_token(&stream, &event, &state, &range, has_range, buf);
            }

            if (state.tbl) {
                blobmsg_close_table(buf, state.tbl);
                state.tbl = NULL;
            }

            free(buffer);
            return 0;
        }
    }

error_out:
    if (buffer)
        free(buffer);
   
    if (state.tbl) {
        blobmsg_close_table(buf, state.tbl);
        state.tbl = NULL;
    }
   
    return -1;
}


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

int iwlist(const char *ifname, struct blob_buf *buf)
{
    int r = 0;
    int skfd;     /* generic raw socket desc. */

    /* Create a channel to the NET kernel. */
    if ((skfd = iw_sockets_open()) < 0) {
        perror("Unable to create a channel to the NET kernel.");
        return -1;
    }
 
    r = print_scanning_info(skfd, ifname, buf);

    /* Close the socket. */
    iw_sockets_close(skfd);

    return r;
}
