#include "ocpp16_client.h"
#include "ocpp16_encode.h"
#include "ocpp16_decode.h"
#include "cJSON.h"
#include "ocpp16.h"
#include "mlogger.h"
#include "ocpp16_configkey.h"
#include "ocpp16_authorization.h"
#include "ocpp16_OfflineChargingRecords.h"
#include "ocpp16_ChargingProfiles.h"
#include "base64.h"
#include "hmac_sha1.h"

static void generate_random_string(uint8_t* stream, int N)
{
    int i;
    uint8_t temp;
    srand((int32_t)time(0));
    for (i = 0; i < N; i++)
    {
        temp = (uint8_t)(rand() % 256);
        if (temp == 0) //随机数不要0
            temp = 128;
        stream[i] = temp;
    }
}

static int ocpp16_websocket_shutdown(ocpp_client_t* dev)
{
    shutdown(dev->fd, SHUT_RDWR);
    close(dev->fd);
    dev->fd = -1;
    dev->debug = 1;
    sprintf(dev->online,"offline");
    dev->connected = 0;
    dev->handshaked = 0;
    dev->boot_notification_readay = 0;
    dev->ocpp16_offlineChargingRecords_uploaded = 0;
    if (dev->fp[0] > 0)
    {
        shutdown(dev->fp[0], SHUT_RDWR);
        close(dev->fp[0]);
        dev->fp[0] = -1;
    }
    if (dev->fp[1] > 0)
    {
        shutdown(dev->fp[1], SHUT_RDWR);
        close(dev->fp[1]);
        dev->fp[1] = -1;
    }
    pthread_cond_broadcast(&dev->cond_rx);//通知所有等待的队列，清空socket的buff
    return -1;
}

static int ocpp16_websocket_send_http_request(ocpp_client_t* dev, const char* server_ip, short server_port, const char* path)
{
    uint8_t stream[1024];
    uint8_t random_key[32];
    uint8_t base64_key[96];
    generate_random_string(random_key, 16);
    base64_encode(base64_key, random_key, 16);
    const char http_request[] =
        "GET %s HTTP/1.1\r\n"
        "Pragma: no-cache\r\n"
        "Cache-Control: no-cache\r\n"
        "Host: %s:%d\r\n"
        "Upgrade: websocket\r\n"
        "Connection: Upgrade\r\n"
        "Sec-WebSocket-Key: %s\r\n"
        "Sec-WebSocket-Protocol: %s\r\n"
        "Sec-WebSocket-Version: 13\r\n\r\n";
        
    int txbyte = sprintf(stream, http_request, path, server_ip, server_port, base64_key, dev->Sec_WebSocket_Protocol);
    printv("send http(%d):\n%s", txbyte, stream);
    int ret = send(dev->fd, stream, txbyte, 0);
    if (ret == 0 || (ret == -1 && errno != EINTR && errno != ETIMEDOUT  && errno != EAGAIN  && errno != EWOULDBLOCK)) {  
        printe("ret:%d, errno:%d\n", ret, errno);
        return ocpp16_websocket_shutdown(dev);
    }
    return ret;
}

static int ocpp16_websocket_send_http_response(ocpp_client_t* dev, const char* request_key, const char* support_protocol)
{
    const char GUID[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
    const char http_response[] =
        "HTTP/1.1 101 Switching Protocols\r\n"
        "Upgrade: websocket\r\n"
        "Server: Microsoft-HTTPAPI/2.0\r\n"
        "Connection: Upgrade\r\n"
        "Sec-WebSocket-Accept: %s\r\n"
        "Sec-WebSocket-Protocol: %s\r\n"
        "Date: %s\r\n\r\n"; //时间打包待续, 格式如 "Date: Tue, 20 Jun 2017 08:50:41 CST\r\n"

    uint8_t stream[2048];
    uint8_t key0[1024];
    uint8_t key1[1024];
    
    sprintf(key0, "%s%s", request_key, GUID);
    int key1L = SHA1_Hash(key1, key0);
    int key0L = base64_encode(key0, key1, key1L);
    time_t now = time(NULL);
    struct tm *ptm = localtime(&now);
    strftime(key1, sizeof(key1), "%a, %d %b %Y %T %Z", ptm);
    int txbyte = sprintf(stream, http_response, key0, support_protocol, key1);
    printv("http response(%d):\n%s", txbyte, stream);
    int ret = send(dev->fd, stream, txbyte, 0);
    if (ret == 0 || (ret == -1 && errno != EINTR && errno != ETIMEDOUT  && errno != EAGAIN  && errno != EWOULDBLOCK)) {  
        printe("ret:%d, errno:%d\n", ret, errno); 
        return ocpp16_websocket_shutdown(dev);
    }
    return ret;
}

static int ocpp16_websocket_read_http_frame(ocpp_client_t* dev, int timeout)
{
    int FRAMELEN = 4096;
    dev->rxbyte = 0;
    while(dev->rxbyte < FRAMELEN)
    {
        int ret = recv(dev->fd, &dev->rxbuff[dev->rxbyte], 1, 0);
        if (ret == 0 || (ret == -1 && errno != EINTR && errno != ETIMEDOUT  && errno != EAGAIN  && errno != EWOULDBLOCK)) {  
            printe("ret:%d, errno:%d\n", ret, errno); 
            return ocpp16_websocket_shutdown(dev);
        }
        dev->rxbyte += ret;
        if (dev->rxbyte > 4)
        {
            
            if (dev->rxbuff[dev->rxbyte-4]=='\r' &&
                dev->rxbuff[dev->rxbyte-3]=='\n' &&
                dev->rxbuff[dev->rxbyte-2]=='\r' &&
                dev->rxbuff[dev->rxbyte-1]=='\n')
            {
                break;
            }
        }
        if (dev->rxbyte>=4 && strncmp(&dev->rxbuff[0], "HTTP", 4)==0)
        {
            continue;
        }
        if (dev->rxbyte>=3 && strncmp(&dev->rxbuff[0], "GET", 3)==0)
        {
            continue;
        }
        if (dev->rxbyte>4)
        {
            memmove(&dev->rxbuff[0], &dev->rxbuff[1], dev->rxbyte-1);
            dev->rxbyte--;
        }
    }
    printv("recv http(%d):\n%s", dev->rxbyte, dev->rxbuff);
    return dev->rxbyte;
}


int ocpp16_websocket_client_handshake(ocpp_client_t* dev)
{
    // printd("Enter ... \n");
    ocpp16_websocket_send_http_request(dev, dev->peer_ip, dev->peer_port, dev->path); 

    while(ocpp16_websocket_read_http_frame(dev, 10000) > 0)
    {
        if (strncmp(&dev->rxbuff[0], "HTTP", 4) != 0) continue;

        char* pstr0 = strstr(dev->rxbuff, "Sec-WebSocket-Accept");
        if (pstr0 == NULL) { 
            ocpp16_websocket_send_http_request(dev, dev->server_ip, dev->server_port, dev->path); 
            continue; 
        }
        sscanf(pstr0, "Sec-WebSocket-Accept: %s\r\n", dev->Sec_WebSocket_Accept);

        char* pstr1 = strstr(dev->rxbuff, "Sec-WebSocket-Protocol");
        if (pstr1 == NULL) { 
            ocpp16_websocket_send_http_request(dev, dev->peer_ip, dev->peer_port, dev->path);
            continue; 
        }
        sscanf(pstr1, "Sec-WebSocket-Protocol: %s\r\n", dev->Sec_WebSocket_Protocol);
        dev->handshaked = 1;
        sprintf(dev->online,"online");
        printd("ok.\n");
        return 0;
    }
    return -1;
}

int ocpp16_websocket_client_connect(ocpp_client_t* dev, const char* server_ip, short server_port)
{
    if (dev) 
    {
        dev->fd = -1;
        dev->debug = 0;
        sprintf(dev->online,"offline");
        dev->connected = 0;
        dev->handshaked = 0;
        dev->txtype = 0x81;
        dev->txmask = 1;
        dev->boot_notification_readay = 0;
        dev->ocpp16_offlineChargingRecords_uploaded = 0;
        sprintf(dev->path, "/ocpp/websocket/CentralSystemService/%s", ccu_config.CCU_SN);
        sprintf(dev->Sec_WebSocket_Protocol, "ocpp1.6");

        dev->fd = socket(AF_INET,SOCK_STREAM, 0);
        if (dev->fd > 0)
        {
            //地址可重用设置(有效避免bind超时)
            int enable = 1;
            setsockopt(dev->fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));

            dev->peer_port = server_port;
            sprintf(dev->peer_ip, "%s", server_ip);
            printd("(%s:%d)...\n", dev->peer_ip, dev->peer_port);

            dev->peeraddr.sin_family = AF_INET;
            dev->peeraddr.sin_port = htons(dev->peer_port);
            dev->peeraddr.sin_addr.s_addr = inet_addr(dev->peer_ip);
            
            if (connect(dev->fd, (struct sockaddr *)&dev->peeraddr, sizeof(dev->peeraddr)) == 0)
            {
                dev->connected = 1;
                socketpair(AF_UNIX, SOCK_STREAM, 0, dev->fp);
                printi("connect(%s:%d) ok\n", dev->peer_ip, dev->peer_port);
                return 0;
            }else{
                printd("connect(%s:%d) failed\n", dev->peer_ip, dev->peer_port);
                return -3;
            }
        }
        return -2;
    }
    return -1;
}
static int ocpp16_websocket_encode(uint8_t* out, uint8_t* xin, int length, uint8_t type, uint8_t mask)
{
    int idx = 0;
    uint8_t FIN = 0x80;//FIN = 0x80, 此包为最后一包数据包, FIN = 0x00, 此包之后还有后续数据包
    out[idx++] = type|FIN;
    if (mask)
    {
        out[idx] = 0x80;
    }else{
        out[idx] = 0x00;
    }
    if (length < 126)
    {
        out[idx++] |= (length&0x7F);
    }
    else if (length < 65536)
    {
        out[idx++] |= 126;
        out[idx++] = (uint8_t)((length >> 8) & 0xFF);
        out[idx++] = (uint8_t)((length >> 0) & 0xFF);
    }
    else
    {
        out[idx++] |= 127;
        out[idx++] = 0x00;
        out[idx++] = 0x00;
        out[idx++] = 0x00;
        out[idx++] = 0x00;
        out[idx++] = (uint8_t)((length >> 24) & 0xFF);
        out[idx++] = (uint8_t)((length >> 16) & 0xFF);
        out[idx++] = (uint8_t)((length >> 8) & 0xFF);
        out[idx++] = (uint8_t)((length >> 0) & 0xFF);
    }
    if (mask)
    {
        uint8_t maskkey[4];
        generate_random_string(maskkey, 4);
        out[idx++] = maskkey[0];
        out[idx++] = maskkey[1];
        out[idx++] = maskkey[2];
        out[idx++] = maskkey[3];
        int i;
        for (i=0; i<length; i++)
        {
            int j = i%4;
            out[idx++] = maskkey[j] ^ xin[i];
        }
    }else{
        memcpy(&out[idx], xin, length);
        idx += length;
    }
    return idx;
}

static int ocpp16_websocket_decode(uint8_t* out, uint8_t* xin, int *length, uint8_t* type, uint8_t* mask)
{
    int idx = 2;
    uint8_t FIN = 0x8F; //只考虑FIN==0x80情况
    if (type) *type = xin[0]&FIN;
    if (mask) *mask = xin[1]&0x80;
    int leng = xin[1]&0x7F;
    if (leng == 127)
    {
        *length = 0;
        idx++;idx++;idx++;idx++;
        *length |= (xin[idx++]<<24);
        *length |= (xin[idx++]<<16);
        *length |= (xin[idx++]<<8);
        *length |= (xin[idx++]<<0);
    }
    else if (leng == 126)
    {
        *length = 0;
        *length |= (xin[idx++]<<8);
        *length |= (xin[idx++]<<0);
    }else{
        *length = leng;
    }

    if ( *mask )
    {
        uint8_t maskkey[4];
        maskkey[0] = xin[idx++];
        maskkey[1] = xin[idx++];
        maskkey[2] = xin[idx++];
        maskkey[3] = xin[idx++];
        int i;
        for (i=0; i < *length; i++)
        {
            int j = i%4;
            out[i] = maskkey[j] ^ xin[idx++];
        }
    }else{
        memcpy(out, &xin[idx], *length);
        idx += *length;
    }
    out[ *length ] = '\0';
    return idx;
}

int ocpp16_websocket_send(ocpp_client_t* dev)
{
    if (dev->fd < 0) return -1;
    if (dev->connected == 0) return -1;
    if (dev->handshaked == 0) return -1;
    if (dev->debug || 1) mlog_i(LOG_OCPP, "[%s]:send[%02x, %02x]:%d, %s\n", dev->online, dev->txtype, dev->txmask, dev->txbyte, dev->txbuff);
    int idx = ocpp16_websocket_encode(dev->txstream, dev->txbuff, dev->txbyte, dev->txtype, dev->txmask);
    int ret = send(dev->fd, dev->txstream, idx, 0);
    if (ret == 0 || (ret == -1 && errno != EINTR && errno != ETIMEDOUT  && errno != EAGAIN  && errno != EWOULDBLOCK)) {
        printe("dev->fd:%d, ret:%d, idx:%d, errno:%d\n", dev->fd, ret, idx, errno);
        return ocpp16_websocket_shutdown(dev);
    }else{
        // printi("dev->fd:%d, ret:%d, idx:%d\n", dev->fd, ret, idx);
        dev->debug = 0;
    }
    return ret;
}

int ocpp16_websocket_recv(ocpp_client_t* dev)
{
    if (dev->fd < 0) return -1;
    if (dev->connected == 0) return -1;
    if (dev->handshaked == 0) return -1;
    int FRAMELEN = 2;
    int rxbyte = 0;
    dev->rxstream[rxbyte] = '\0';
    while (rxbyte < FRAMELEN && dev->fd > 0)
    {
        int ret = recv(dev->fd, &dev->rxstream[rxbyte], FRAMELEN - rxbyte, 0);
        if (ret == 0 || (ret == -1 && errno != EINTR && errno != ETIMEDOUT  && errno != EAGAIN  && errno != EWOULDBLOCK)) {
            printe("dev->fd:%d, ret:%d, errno:%d\n", dev->fd, ret, errno);
            return ocpp16_websocket_shutdown(dev);
        }else{
            dev->debug = 0;
        }
        rxbyte += ret;
        // printd("step, rxbyte:%d\n", rxbyte);
        dev->rxstream[rxbyte] = '\0';
        if (rxbyte >= 2)
        {
            if (dev->rxstream[0] == WS_DATATYPE_MINDATA ||
                dev->rxstream[0] == WS_DATATYPE_TXTDATA ||
                dev->rxstream[0] == WS_DATATYPE_BINDATA ||
                dev->rxstream[0] == WS_DATATYPE_DISCONN ||
                dev->rxstream[0] == WS_DATATYPE_PING ||
                dev->rxstream[0] == WS_DATATYPE_PONG)
            {
                uint8_t mask = dev->rxstream[1]&0x80;
                uint8_t leng = dev->rxstream[1]&0x7F;
                int length;
                if (leng == 127)
                {
                    int msb = 0;
                    msb |= (dev->rxstream[2]<<24);
                    msb |= (dev->rxstream[3]<<16);
                    msb |= (dev->rxstream[4]<<8);
                    msb |= (dev->rxstream[5]<<0);

                    length = 0;
                    length |= (dev->rxstream[6]<<24);
                    length |= (dev->rxstream[7]<<16);
                    length |= (dev->rxstream[8]<<8);
                    length |= (dev->rxstream[9]<<0);
                    if (rxbyte<10)
                    {
                        FRAMELEN = 10;
                    }else{
                        FRAMELEN = length + 10;
                    }
                }
                else if (leng == 126)
                {
                    length = 0;
                    length |= (dev->rxstream[2]<<8);
                    length |= (dev->rxstream[3]<<0);
                    FRAMELEN = length + 4;
                    if (rxbyte<4)
                    {
                        FRAMELEN = 4;
                    }else{
                        FRAMELEN = length + 4;
                    }
                }else{
                    length = dev->rxstream[1]&0x7F;
                    FRAMELEN = length + 2;
                }
                if (mask)
                {
                    FRAMELEN = length + 4;
                }
            }else{
                printd("rxstream[0]: %c\n", dev->rxstream[0]);
                memmove(&dev->rxstream[0], &dev->rxstream[1], rxbyte-1);
                rxbyte--;
            }
        }
    }
    // printd("step FRAMELEN:%d, rxbyte:%d\n", FRAMELEN, rxbyte);
    dev->rxstream[rxbyte] = '\0';
    int idx = ocpp16_websocket_decode(dev->rxbuff, dev->rxstream, &dev->rxbyte, &dev->rxtype, &dev->rxmask);
    rxbyte -= idx;
    if (dev->debug || 1) mlog_i(LOG_OCPP, "[%s]:recv[%02x, %02x]:%d, %s\n", dev->online, dev->rxtype, dev->rxmask, dev->rxbyte, dev->rxbuff);
    if (dev->rxtype == WS_DATATYPE_PING)
    {
        dev->txbyte = sprintf(dev->txbuff, "pong");
        if (dev->debug || 1) mlog_i(LOG_OCPP, "[%s]:send[%02x, %02x]:%d, %s\n", dev->online, WS_DATATYPE_PONG, dev->txmask, dev->txbyte, dev->txbuff);
        int idx = ocpp16_websocket_encode(dev->txstream, dev->txbuff, dev->txbyte, WS_DATATYPE_PONG, dev->txmask);
        int ret = send(dev->fd, dev->txstream, idx, 0);
        if (ret == 0 || (ret == -1 && errno != EINTR && errno != ETIMEDOUT  && errno != EAGAIN  && errno != EWOULDBLOCK)) {
            printe("ret:%d, errno:%d\n", ret, errno);
            return ocpp16_websocket_shutdown(dev);
        }else{
            dev->debug = 0;
        }
        return ret;
    }
    return dev->rxbyte;
}

int ocpp16_websocket_recv_timeout(ocpp_client_t* dev, int timeout)
{
    if (dev->fd < 0) return -1;
    if (dev->connected == 0) return -1;
    if (dev->handshaked == 0) return -1;

    fd_set rset;
    struct timeval tv;
    int FRAME_LENGTH = 8;
    tv.tv_sec = timeout/1000;
    tv.tv_usec = (timeout%1000)*10000;
    FD_ZERO(&rset);
    FD_SET(dev->fd, &rset);
    int ret = select(dev->fd + 1, &rset, NULL, NULL, &tv);
    if (ret > 0)
    {
        return ocpp16_websocket_recv(dev);
    }
    if (ret == -1 && errno != EINTR && errno != ETIMEDOUT  && errno != EAGAIN  && errno != EWOULDBLOCK)
    {
        printe("ret:%d, errno:%d\n", ret, errno);
        return ocpp16_websocket_shutdown(dev);
    }
    return -1;
}
void ocpp16_websocket_exit(ocpp_client_t* dev)
{
    if (dev)
    {
        if (dev->fd > 0)
        {
            ocpp16_websocket_shutdown(dev);
        }
    }
}