/******************************************************************************
  @file    atc.c
  @brief   at command.

  DESCRIPTION
  Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules.

  INITIALIZATION AND SEQUENCING REQUIREMENTS
  None.

  ---------------------------------------------------------------------------
  Copyright (c) 2016 - 2023 Quectel Wireless Solution, Co., Ltd.  All Rights Reserved.
  Quectel Wireless Solution Proprietary and Confidential.
  ---------------------------------------------------------------------------
******************************************************************************/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <stddef.h>
#include <pthread.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <getopt.h>
#include <poll.h>
#include <sys/time.h>
#include <endian.h>
#include <time.h>
#include <sys/types.h>
#include <limits.h>
#include <inttypes.h>

extern int asprintf(char **s, const char *fmt, ...);

#include "QMIThread.h"

#include "atchannel.h"
#include "at_tok.h"

static int asr_style_atc = 0;
static int s_pdp;
#define safe_free(__x) do { if (__x) { free((void *)__x); __x = NULL;}} while(0)
#define safe_at_response_free(__x) { if (__x) { at_response_free(__x); __x = NULL;}}

#define at_response_error(err, p_response) \
    (err \
    || p_response == NULL \
    || p_response->finalResponse == NULL \
    || p_response->success == 0)

static int atc_init(PROFILE_T *profile) {
    int err;
    char *cmd;
    ATResponse *p_response = NULL;

    if (profile->proxy[0])  {
        s_pdp = profile->pdp;
        err = at_send_command_singleline("AT+QNETDEVSTATUS=?", "+QNETDEVSTATUS:", &p_response);
        if (at_response_error(err, p_response))
            asr_style_atc = 1; //EC200T/EC100Y do not support this AT, but RG801/RG500U support
        safe_at_response_free(p_response);
		
        return err;
    }

    err = at_handshake();
    if (err) {
        dbg_time("handshake fail, TODO ... ");
        goto exit;
    }
	
    s_pdp = profile->pdp;
    at_send_command_singleline("AT+QCFG=\"usbnet\"", "+QCFG:", NULL);
    at_send_command_multiline("AT+QNETDEVCTL=?", "+QNETDEVCTL:", NULL);
    at_send_command("AT+CGREG=2", NULL); //GPRS Network Registration Status
    at_send_command("AT+CEREG=2", NULL); //EPS Network Registration Status
    at_send_command("AT+C5GREG=2", NULL); //5GS Network Registration Status

    err = at_send_command_singleline("AT+QNETDEVSTATUS=?", "+QNETDEVSTATUS:", &p_response);
    if (at_response_error(err, p_response))
        asr_style_atc = 1; //EC200T/EC100Y do not support this AT, but RG801/RG500U support 
    safe_at_response_free(p_response);

    err = at_send_command_singleline("AT+QCFG=\"NAT\"", "+QCFG:", &p_response);
    if (!at_response_error(err, p_response)) {
        int old_nat, new_nat = asr_style_atc ? 1 : 0;

        err = at_tok_scanf(p_response->p_intermediates->line, "%s%d", NULL, &old_nat);
        if (err == 2 && old_nat != new_nat) {
            safe_at_response_free(p_response);
            asprintf(&cmd, "AT+QCFG=\"NAT\",%d", new_nat);
            err = at_send_command(cmd, &p_response);
            safe_free(cmd);
            if (!at_response_error(err, p_response)) {
                err = at_send_command("at+cfun=1,1",NULL);
                if (!err)
                    g_donot_exit_when_modem_hangup = 1;
                //reboot to take effect
            }
            safe_at_response_free(p_response);
        }
        err = 0;
    }
    safe_at_response_free(p_response);

exit:
    return err;
}

static int atc_deinit(void) {
    return 0;
}

/**
 * Called by atchannel when an unsolicited line appears
 * This is called on atchannel's reader thread. AT commands may
 * not be issued here
 */
static void onUnsolicited (const char *s, const char *sms_pdu)
{
    (void)sms_pdu;

    if (strStartsWith(s, "+QNETDEVSTATUS:")) {
        qmidevice_send_event_to_main(RIL_UNSOL_DATA_CALL_LIST_CHANGED);
    }
    else if (strStartsWith(s, "+CGREG:")
        || strStartsWith(s, "+CEREG:")
        || strStartsWith(s, "+C5GREG:")) {
        qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED);
    }
}

static void onTimeout(void) {
    dbg_time("%s", __func__);
    //TODO
}

static void onClose(void) {
    dbg_time("%s", __func__);
}

static void * atc_read_thread(void *param) {
    PROFILE_T *profile = (PROFILE_T *)param;
    const char *cdc_wdm = (const char *)profile->qmichannel;
    int wait_for_request_quit = 0;
    int atc_fd;

    atc_fd = cm_open_dev(cdc_wdm);
    if (atc_fd <= 0) {
        dbg_time("fail to open (%s), errno: %d (%s)", cdc_wdm, errno, strerror(errno));
        goto __quit;
    }

    dbg_time("atc_fd = %d", atc_fd);

    if (at_open(atc_fd, onUnsolicited, 0))
        goto __quit;

    at_set_on_timeout(onTimeout);
    at_set_on_reader_closed(onClose);
    qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED);

    while (atc_fd > 0) {
        struct pollfd pollfds[] = {{atc_fd, POLLIN, 0}, {qmidevice_control_fd[1], POLLIN, 0}};
        int ne, ret, nevents = 2;

        ret = poll(pollfds, nevents, wait_for_request_quit ? 1000 : -1);

        if (ret == 0 && wait_for_request_quit) {
            break;
        }

        if (ret < 0) {
            dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno));
            break;
        }

        for (ne = 0; ne < nevents; ne++) {
            int fd = pollfds[ne].fd;
            short revents = pollfds[ne].revents;

            if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
                dbg_time("%s poll err/hup/inval", __func__);
                dbg_time("epoll fd = %d, events = 0x%04x", fd, revents);
                if (revents & (POLLERR | POLLHUP | POLLNVAL))
                goto __quit;
            }

            if ((revents & POLLIN) == 0)
                continue;

            if (atc_fd == fd) {
                usleep(10*1000); //let atchannel.c read at response.
            }
            else if (fd == qmidevice_control_fd[1]) {
                int triger_event;
                if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) {
                    //dbg_time("triger_event = 0x%x", triger_event);
                    switch (triger_event) {
                        case RIL_REQUEST_QUIT:
                            goto __quit;
                        break;
                        case SIG_EVENT_STOP:
                            wait_for_request_quit = 1;
                        break;
                        default:
                        break;
                    }
                }
            }
        }
    }

__quit:
    at_close();
    qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED);
    dbg_time("%s exit", __func__);

    return NULL;
}

const struct qmi_device_ops atc_dev_ops = {
    .init = atc_init,
    .deinit = atc_deinit,
    .read = atc_read_thread,
};

static int requestBaseBandVersion(PROFILE_T *profile) {
    int retVal = -1;
    int err;
    ATResponse *p_response = NULL;

    (void)profile;

    err = at_send_command_multiline("AT+CGMR", "\0", &p_response);
    if (at_response_error(err, p_response))
        goto exit;

    if (p_response->p_intermediates && p_response->p_intermediates->line) {
        strncpy(profile->BaseBandVersion, p_response->p_intermediates->line, sizeof(profile->BaseBandVersion) - 1);
        retVal = 0;
    }

exit:
    safe_at_response_free(p_response);
    return retVal;
}

static int requestGetSIMStatus(SIM_Status *pSIMStatus)
{
    int err;
    ATResponse *p_response = NULL;
    char *cpinLine;
    char *cpinResult;
    int ret = SIM_NOT_READY;

    err = at_send_command_singleline("AT+CPIN?", "+CPIN:", &p_response);
    if (at_response_error(err, p_response))
        goto done;

    switch (at_get_cme_error(p_response))
    {
    case CME_SUCCESS:
        break;

    case CME_SIM_NOT_INSERTED:
    case CME_OPERATION_NOT_ALLOWED:
    case CME_FAILURE:
        ret = SIM_ABSENT;
        goto done;

    default:
        ret = SIM_NOT_READY;
        goto done;
    }

    cpinLine = p_response->p_intermediates->line;
    err = at_tok_start (&cpinLine);

    if (err < 0)
    {
        ret = SIM_NOT_READY;
        goto done;
    }

    err = at_tok_nextstr(&cpinLine, &cpinResult);

    if (err < 0)
    {
        ret = SIM_NOT_READY;
        goto done;
    }

    if (0 == strcmp (cpinResult, "SIM PIN"))
    {
        ret = SIM_PIN;
        goto done;
    }
    else if (0 == strcmp (cpinResult, "SIM PUK"))
    {
        ret = SIM_PUK;
        goto done;
    }
    else if (0 == strcmp (cpinResult, "PH-NET PIN"))
    {
        return SIM_NETWORK_PERSONALIZATION;
    }
    else if (0 != strcmp (cpinResult, "READY"))
    {
        /* we're treating unsupported lock types as "sim absent" */
        ret = SIM_ABSENT;
        goto done;
    }

    ret = SIM_READY;

done:
    safe_at_response_free(p_response);
    *pSIMStatus = ret;
    return err;
}

static int requestEnterSimPin(const char *pPinCode) {
    int retVal = -1;
    int err;
    ATResponse *p_response = NULL;
    char *cmd = NULL;

    asprintf(&cmd, "AT+CPIN=%s", pPinCode);
    err = at_send_command(cmd, NULL);
    safe_free(cmd);

    if (!at_response_error(err, p_response)) {
        retVal = 0;
    }

    safe_at_response_free(p_response);
    return retVal;
}

static int requestSetProfile(PROFILE_T *profile) {
    int err;
    ATResponse *p_response = NULL;
    char *cmd = NULL;
    const char *new_apn = profile->apn ? profile->apn : "";
    const char *new_user = profile->user ? profile->user : "";
    const char *new_password = profile->password ? profile->password : "";
    const char *ipStr[] = {"NULL", "IPV4", "IPV6", "IPV4V6"};

    dbg_time("%s[%d] %s/%s/%s/%d/%s", __func__,
        profile->pdp, profile->apn, profile->user, profile->password,
        profile->auth,ipStr[profile->iptype]);

    if (profile->force_apn_set) {
        dbg_time("clear APN settings");
        asprintf(&cmd, "AT+QICSGP=%d,%d,\"\",\"\",\"\",0", profile->pdp, profile->iptype);
        err = at_send_command(cmd, &p_response);
        safe_free(cmd);
        if (at_response_error(err, p_response)) {
            safe_at_response_free(p_response);
            return -1;
        }
    }

    if (!profile->force_apn_set && !strcmp(profile->old_apn, new_apn) && !strcmp(profile->old_user, new_user)
        && !strcmp(profile->old_password, new_password)
        && profile->old_iptype == profile->iptype
        && profile->old_auth == profile->auth)
    {
        dbg_time("no need to set skip the rest");
        return 0;
    }

    if (profile->force_apn_set) {
        dbg_time("Force APN setting enabled, proceeding with profile update");
    }

    asprintf(&cmd, "AT+QICSGP=%d,%d,\"%s\",\"%s\",\"%s\",%d",
        profile->pdp, profile->iptype, new_apn, new_user, new_password, profile->auth);
    err = at_send_command(cmd, &p_response);
    safe_free(cmd);
    if (at_response_error(err, p_response)) {
        safe_at_response_free(p_response);
        asprintf(&cmd, "AT+CGDCONT=%d,\"%s\",\"%s\"", profile->pdp, ipStr[profile->iptype], new_apn);
        err = at_send_command(cmd, &p_response);
        safe_free(cmd);
    }

    safe_at_response_free(p_response);
    return 1;
}

static int requestGetProfile(PROFILE_T *profile) {
    int retVal = -1;
    int err;
    ATResponse *p_response = NULL;
    char *cmd = NULL;
    int pdp;
    int old_iptype = 1; // 1 ~ IPV4, 2 ~ IPV6, 3 ~ IPV4V6
    char *old_apn = "", *old_user = "", *old_password = "";
    int old_auth = 0;
    const char *ipStr[] = {"NULL", "IPV4", "IPV6", "IPV4V6"};

    if (profile->enable_ipv4 && profile->enable_ipv6)
        profile->iptype = 3;
    else if (profile->enable_ipv6)
        profile->iptype = 2;
    else
        profile->iptype = 1;

_re_check:
    asprintf(&cmd, "AT+QICSGP=%d", profile->pdp);
    err = at_send_command_singleline(cmd, "+QICSGP:", &p_response);
    safe_free(cmd);
    if (err == AT_ERROR_INVALID_RESPONSE && p_response == NULL) {
        //bug of RG801H
        safe_at_response_free(p_response);
        asprintf(&cmd, "AT+QICSGP=%d,%d,\"\",\"\",\"\",0", profile->pdp, profile->iptype);
        err = at_send_command(cmd, &p_response);
        safe_free(cmd);
        if (!at_response_error(err, p_response)) {
            safe_at_response_free(p_response);
            goto _re_check;
        }
    }

    if (!at_response_error(err, p_response)) {
        err = at_tok_scanf(p_response->p_intermediates->line,
            "%d%s%s%s%d", &old_iptype, &old_apn,  &old_user, &old_password, &old_auth);

        if (err != 4 || pdp != profile->pdp)
            goto _error;
    }
    else {
        ATLine *atLine = NULL;
        char *cgdcont_iptype = NULL;

        safe_at_response_free(p_response);
        err = at_send_command_multiline("AT+CGDCONT?", "+CGDCONT:", &p_response);
        if (at_response_error(err, p_response))
            goto _error;

        atLine = p_response->p_intermediates;
        while (atLine) {
            err = at_tok_scanf(atLine->line, "%d%s%s", &pdp, &cgdcont_iptype, &old_apn);
            if (err == 3 && pdp == profile->pdp) {
                if (!strcasecmp(cgdcont_iptype, ipStr[3]))
                    old_iptype = 3;
                else if (!strcasecmp(cgdcont_iptype, ipStr[2]))
                     old_iptype = 2;
                else
                    old_iptype = 1;
               break;
            }
            old_apn = NULL;
            atLine = atLine->p_next;
        }
    }

    retVal = 0;

_error:
    if (!old_apn) old_apn = "";
    if (!old_user) old_user = "";
    if (!old_password) old_password = "";

    strncpy(profile->old_apn, old_apn, sizeof(profile->old_apn));
    strncpy(profile->old_user, old_user, sizeof(profile->old_user));
    strncpy(profile->old_password, old_password, sizeof(profile->old_password));
    profile->old_auth = old_auth;
    profile->old_iptype = old_iptype; 

    dbg_time("%s[%d] %s/%s/%s/%d/%s", __func__,
        profile->pdp, profile->old_apn, profile->old_user, profile->old_password,
        profile->old_auth, ipStr[profile->old_iptype]);

    safe_at_response_free(p_response);

    return retVal;
}

static int requestRegistrationState(UCHAR *pPSAttachedState) {
    int retVal = -1;
    int err;
    ATResponse *p_response = NULL;
    ATLine *p_cur;
    int i;
    int cops_act = -1;
    int state = 0, lac = 0, cid = 0, act = 0;
    int commas;
    char *line;

    *pPSAttachedState = 0;

    err = at_send_command_multiline(
              "AT+COPS=3,0;+COPS?;+COPS=3,1;+COPS?;+COPS=3,2;+COPS?",
              "+COPS:", &p_response);
    if (at_response_error(err, p_response))
        goto error;

/*
AT< +COPS: 0,0,"CHINA MOBILE",13
AT< +COPS: 0,1,"CMCC",13
AT< +COPS: 0,2,"46000",13
AT< OK
*/
    retVal = 0;
    for (i = 0, p_cur = p_response->p_intermediates; p_cur != NULL; p_cur = p_cur->p_next, i++) {
        err = at_tok_scanf(p_cur->line, "%d%d%s%d", NULL, NULL, NULL, &cops_act);
        if (err != 4) goto error;

        break;
    }

    safe_at_response_free(p_response);
    switch (cops_act) {
        case 2: //UTRAN
        case 3: //GSM W/EGPRS
        case 4: //UTRAN W/HSDPA
        case 5: //UTRAN W/HSUPA
        case 6: //UTRAN W/HSDPA and HSUPA
            //AT+CGREG  GPRS Network Registration Status
            err = at_send_command_singleline("AT+CGREG?", "+CGREG:", &p_response);
        break;

        case 7: //E-UTRAN
        case 13: //E-UTRAN-NR dual connectivity
            //AT+CEREG  EPS Network Registration Status
            err = at_send_command_singleline("AT+CEREG?", "+CEREG:", &p_response);
        break;

        case 10: //E-UTRAN connected to a 5GCN
        case 11: //NR connected to a 5GCN
        case 12: //NG-RAN
            //AT+C5GREG  5GS Network Registration Status
            err = at_send_command_singleline("AT+C5GREG?", "+C5GREG:", &p_response);
        break;

        default:
            goto error;
        break;
    }

    if (at_response_error(err, p_response))
        goto error;
    if (!p_response->p_intermediates || !p_response->p_intermediates->line) goto error;

    line = p_response->p_intermediates->line;
    commas = at_tok_count(line);
     switch (commas) {
        case 0: /* +CREG: <stat> */
            err = at_tok_nextint(&line, &state);
            if (err < 0) goto error;
            break;

        case 1: /* +CREG: <n>, <stat> */
            err = at_tok_scanf(line, "%d%d", NULL, &state);
            if (err != 2) goto error;
            break;

        case 2: /* +CREG: <stat>, <lac>, <cid> */
            err = at_tok_scanf(line, "%d%x%x", NULL, &state, &lac, &cid);
            if (err != 3) goto error;
            break;

        case 3: /* +CREG: <n>, <stat>, <lac>, <cid> */
            err = at_tok_scanf(line, "%d%d%x%x", NULL, &state, &lac, &cid);
            if (err != 4) goto error;
            break;

        case 4: //<n>, <stat>, <lac>, <cid>, <networkType> */
        case 5:
        case 6: 
        case 7:
            err = at_tok_scanf(line, "%d%d%x%x%d", NULL, &state, &lac, &cid, &act);
            if (err != 5) goto error;
        break;

        default:
            goto error;
    }

    //dbg_time("state=%d", state);

    if (state == 1 || state == 5) { //Registered, home network  / roaming
        *pPSAttachedState = 1;
    }

error:
    safe_at_response_free(p_response);
    return retVal;
}

static int requestSetupDataCall(PROFILE_T *profile, int curIpFamily) {
    int err;
    ATResponse *p_response = NULL;
    char *cmd = NULL;
    ATLine *p_cur = NULL;
    int pdp = profile->pdp;
    int state = 0;

    (void)curIpFamily;

    if (strStartsWith(profile->BaseBandVersion, "RG801H") || strStartsWith(profile->BaseBandVersion, "EC200H")) {
        //RG801H will miss USB_CDC_NOTIFY_NETWORK_CONNECTION
        asprintf(&cmd, "ifconfig %s up", profile->usbnet_adapter);
        if (system(cmd)) {};
        safe_free(cmd);
    }

    if (asr_style_atc) {
        err = at_send_command_multiline("AT+CGACT?", "+CGACT:", &p_response);
        if (at_response_error(err, p_response))
            goto _error;

        for (p_cur = p_response->p_intermediates; p_cur != NULL; p_cur = p_cur->p_next) {
            int cid = 0;
			state = 0;
			
            err = at_tok_scanf(p_cur->line, "%d%d", &cid, &state);
            if (cid == pdp)
                break;
			else if(state)
				state = 0;
        }
        safe_at_response_free(p_response);

        if (state == 0) {
            asprintf(&cmd, "AT+CGACT=1,%d", pdp);
            err = at_send_command(cmd, &p_response);
            safe_free(cmd);
            if (at_response_error(err, p_response))
                goto _error;
        }
    }

    if(asr_style_atc)
        asprintf(&cmd, "AT+QNETDEVCTL=1,%d,%d", pdp, 1);
    else
        asprintf(&cmd, "AT+QNETDEVCTL=%d,1,%d", pdp, 1);
    err = at_send_command(cmd, &p_response);
    safe_free(cmd);

    if (at_response_error(err, p_response))
        goto _error;

    if (!asr_style_atc) { //TODO some modems do not sync return setup call resule
        int t = 0;

        while (t++ < 15) {
            asprintf(&cmd, "AT+QNETDEVSTATUS=%d", pdp);
            err = at_send_command_singleline(cmd, "+QNETDEVSTATUS", &p_response);
            safe_free(cmd);
            if (err) goto _error;

            if (!at_response_error(err, p_response)) {
                break;
            }
            safe_at_response_free(p_response);
            sleep(1);
        }
    }

    //some modem do not report URC
    qmidevice_send_event_to_main(RIL_UNSOL_DATA_CALL_LIST_CHANGED);

_error:
    safe_at_response_free(p_response);
    //dbg_time("%s err=%d", __func__, err);
    return err;
}

static int at_netdevstatus(int pdp, unsigned int *pV4Addr) {
    int err;
    ATResponse *p_response = NULL;
    char *cmd = NULL;
    char *ipv4_address = NULL;
    char *ipv4_gate = NULL;
    char *ipv4_DHCP = NULL;
    char *ipv4_pDNS = NULL;
    char *ipv4_sDNS = NULL;
    char *ipv6_address = NULL;
    char *ipv6_gate = NULL;
    char *ipv6_DHCP = NULL;
    char *ipv6_pDNS = NULL;
    char *ipv6_sDNS = NULL;

    *pV4Addr = 0;

    asprintf(&cmd, "AT+QNETDEVSTATUS=%d", pdp);
    err = at_send_command_singleline(cmd, "+QNETDEVSTATUS", &p_response);
    safe_free(cmd);
    if (at_response_error(err, p_response)) goto _error;
    if (!p_response->p_intermediates || !p_response->p_intermediates->line) goto _error;

    err = at_tok_scanf(p_response->p_intermediates->line, "%s%s%s%s%s%s%s%s%s%s",
        &ipv4_address, &ipv4_gate, &ipv4_DHCP, &ipv4_pDNS, &ipv4_sDNS,
        &ipv6_address, &ipv6_gate, &ipv6_DHCP, &ipv6_pDNS, &ipv6_sDNS);
    if (err > 0) {
#if 0
        dbg_time("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s",
            ipv4_address, ipv4_gate, ipv4_DHCP, ipv4_pDNS, ipv4_sDNS,
            ipv6_address, ipv6_gate, ipv6_DHCP, ipv6_pDNS, ipv6_sDNS);
#endif

        if (ipv4_address && ipv4_address[0]) {
            int addr[4] = {0, 0, 0, 0};

            if (strstr(ipv4_address, ".")) {
                sscanf(ipv4_address, "%d.%d.%d.%d", &addr[0], &addr[1], &addr[2], &addr[3]);
            }
            else {
                sscanf(ipv4_address, "%02X%02X%02X%02X", &addr[3], &addr[2], &addr[1], &addr[0]);
            }
            *pV4Addr = (addr[0]) | (addr[1]<<8) | (addr[2]<<16) | (addr[3]<<24);
       }
    }

_error:
    safe_at_response_free(p_response);
    return 0;
}

static int requestQueryDataCall(UCHAR  *pConnectionStatus, int curIpFamily) {
    int err;
    ATResponse *p_response = NULL;
    ATLine *p_cur = NULL;
    int state = 0;
    int bind = 0;
    int cid;
    int pdp = s_pdp;
    unsigned int v4Addr = 0;

    (void)curIpFamily;

    *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED;

    if (!asr_style_atc) {
        err = at_netdevstatus(pdp, &v4Addr);
        if (!err && v4Addr) {
            *pConnectionStatus = QWDS_PKT_DATA_CONNECTED;
            //if (profile->ipv4.Address == v4Addr) {} //TODO
        }
        return err;
    }

    err = at_send_command_multiline("AT+QNETDEVCTL?", "+QNETDEVCTL:", &p_response);
    if (at_response_error(err, p_response))
        goto _error;

    for (p_cur = p_response->p_intermediates; p_cur != NULL; p_cur = p_cur->p_next)
    {
        //+QNETDECTL:<op>,<cid>,<urc_en>,<state>
        err = at_tok_scanf(p_cur->line, "%d%d%d%d", &bind, &cid, NULL, &state);
        if (err != 4 || cid != pdp)
            continue;
        if (bind != 1)
            bind = 0;
    }
    safe_at_response_free(p_response);

    if (bind == 0 || state == 0)
        goto _error;

    err = at_send_command_multiline("AT+CGACT?", "+CGACT:", &p_response);
    if (at_response_error(err, p_response))
        goto _error;

    for (p_cur = p_response->p_intermediates; p_cur != NULL; p_cur = p_cur->p_next)
    {
		state = 0;
        err = at_tok_scanf(p_cur->line, "%d%d", &cid, &state);
        if (cid == pdp)
            break;
		else if(state)
			state = 0;
    }
    safe_at_response_free(p_response);

    if (bind && state)
        *pConnectionStatus = QWDS_PKT_DATA_CONNECTED;

_error:
    safe_at_response_free(p_response);
    //dbg_time("%s err=%d, call_state=%d", __func__, err, *pConnectionStatus);
    return 0;
}

static int requestDeactivateDefaultPDP(PROFILE_T *profile, int curIpFamily) {
    char *cmd = NULL;
    int pdp = profile->pdp;

    (void)curIpFamily;

    if (asr_style_atc)
        asprintf(&cmd, "AT+QNETDEVCTL=0,%d,%d", pdp, 0);
    else
        asprintf(&cmd, "AT+QNETDEVCTL=%d,0,%d", pdp, 0);
    at_send_command(cmd, NULL);
    safe_free(cmd);

    //dbg_time("%s err=%d", __func__, err);
    return 0;
}

static int requestGetIPAddress(PROFILE_T *profile, int curIpFamily) {
    int err;
    ATResponse *p_response = NULL;
    char *cmd = NULL;
    ATLine *p_cur = NULL;
    int pdp = profile->pdp;
    unsigned int v4Addr = 0;

    (void)curIpFamily;

    if (!asr_style_atc) {
        err = at_netdevstatus(pdp, &v4Addr);
        goto _error;
    }

    asprintf(&cmd, "AT+CGPADDR=%d", profile->pdp);
    err = at_send_command_singleline(cmd, "+CGPADDR:", &p_response);
    safe_free(cmd);
    if (at_response_error(err, p_response))
        goto _error;

    //+CGPADDR: 1,"10.201.80.91","2409:8930:4B3:41C7:F9B8:3D9B:A2F7:CA96"
    for (p_cur = p_response->p_intermediates; p_cur != NULL; p_cur = p_cur->p_next)
    {
        char *ipv4 = NULL;
        char *ipv6 = NULL;

        err = at_tok_scanf(p_cur->line, "%d%s%s", &pdp, &ipv4, &ipv6);
        if (err < 2 || pdp != profile->pdp)
            continue;

        if (ipv4) {
            int addr[4] = {0, 0, 0, 0};

            sscanf(ipv4, "%d.%d.%d.%d", &addr[0], &addr[1], &addr[2], &addr[3]);
            v4Addr = (addr[0]) | (addr[1]<<8) | (addr[2]<<16) | (addr[3]<<24);
            break;
        }
    }

_error:
    if (v4Addr && profile->ipv4.Address != v4Addr) {
        unsigned char *v4 = (unsigned char *)&v4Addr;

        profile->ipv4.Address = v4Addr;
        dbg_time("%s %d.%d.%d.%d", __func__, v4[0], v4[1], v4[2], v4[3]);    
    }
        
    //dbg_time("%s err=%d", __func__, err);
    return v4Addr ? 0 : -1;
}

static int requestGetSignalInfo(void) {
    int retVal = -1;
    int err;
    ATResponse *p_response = NULL;
    int i;
    ATLine *p_cur = NULL;
    char *rat = NULL;
    int cops_act = 0;
    int is_nr5g_nsa = 0, nr5g_sa = 0;
    int verbose = 0;

    err = at_send_command_singleline("at+cops?", "+COPS:", &p_response);
    if (at_response_error(err, p_response)) goto _error;
    if (!p_response->p_intermediates || !p_response->p_intermediates->line) goto _error;

    retVal = 0;
    err = at_tok_scanf(p_response->p_intermediates->line, "%d%d%s%d", NULL, NULL, NULL, &cops_act);
    if (err != 4) goto _error;

    nr5g_sa = (cops_act == 11);

    safe_at_response_free(p_response);
    err = at_send_command_multiline("at+qeng=\"servingcell\"", "+QENG:", &p_response);
    if (at_response_error(err, p_response))
        goto _error;

    for (i = 0, p_cur = p_response->p_intermediates; p_cur != NULL; p_cur = p_cur->p_next, i++) {
        char *type, *state;

        err = at_tok_scanf(p_cur->line, "%s%s", &type, &state);
        if (err != 2 || strcmp(type, "servingcell"))
            continue;

        if (!strcmp(state, "SEARCH") || !strcmp(state, "LIMSRV"))
            continue;

        if (!strcmp(state, "NOCONN") || !strcmp(state, "CONNECT")) {
            err = at_tok_scanf(p_cur->line, "%s%s%s", &type, &state, &rat);
            if (err != 3)
                continue;        
        }
        else {
            rat = state;
        }
       
        if (!strcmp(rat, "NR5G-SA"))
        {
            //+QENG: "servingcell",<state>,"NR5G-SA",<duplex_mode>,<MCC>,<MNC>,<cellID>,<PCID>,<TAC>,<ARFCN>,<band>,<NR_DL_bandwidth>,<RSRP>,<RSRQ>,<SINR>,<tx_power>,<srxlev> 
            //+QENG: "servingcell","NOCONN","NR5G-SA","TDD", 454,12,0,21,4ED,636576,78,3,-85,-11,32,0,5184
            struct qeng_servingcell_nr5g_sa {
                char *cell_type, *state, *rat, *is_tdd;
                int MCC, MNC, cellID/*hex*/;
                int PCID, TAC/*hex*/, ARFCN;
                int band, NR_DL_bandwidth;
                int RSRP, RSRQ, RSSI, SINR;
            };
            struct qeng_servingcell_nr5g_sa nr5g_sa;

            memset(&nr5g_sa, 0, sizeof(nr5g_sa));
            err = at_tok_scanf(p_cur->line, "%s,%s,%s,%s,%d,%d,%x,%d,%x,%d,%d,%d,%d,%d,%d,%d",
                &nr5g_sa.cell_type, &nr5g_sa.state, &nr5g_sa.rat, &nr5g_sa.is_tdd,
                &nr5g_sa.MCC, &nr5g_sa.MNC, &nr5g_sa.cellID, &nr5g_sa.PCID, &nr5g_sa.TAC,
                &nr5g_sa.ARFCN, &nr5g_sa.band, &nr5g_sa.NR_DL_bandwidth,
                &nr5g_sa.RSRP, &nr5g_sa.RSRQ, &nr5g_sa.RSSI, &nr5g_sa.SINR);

            if (err >= 13 && verbose) {
                dbg_time("%s,%s,%s,%s,%d,%d,%x,%d,%x,%d,%d,%d,%d,%d,%d,%d",
                    nr5g_sa.cell_type, nr5g_sa.state, nr5g_sa.rat, nr5g_sa.is_tdd,
                    nr5g_sa.MCC, nr5g_sa.MNC, nr5g_sa.cellID, nr5g_sa.PCID, nr5g_sa.TAC,
                    nr5g_sa.ARFCN, nr5g_sa.band, nr5g_sa.NR_DL_bandwidth,
                    nr5g_sa.RSRP, nr5g_sa.RSRQ, nr5g_sa.RSSI, nr5g_sa.SINR);
            }
        }
        else if (!strcmp(rat, "NR5G-NSA"))
        {
            //+QENG: "NR5G-NSA",<MCC>,<MNC>,<PCID>,<RSRP>,< SINR>,<RSRQ>,<ARFCN>,<band>
            struct qeng_servingcell_nr5g_nsa {
                char *mcc, *mnc;
                int pcid, rsrp, sinr, rsrq;
            };
            struct qeng_servingcell_nr5g_nsa nr5g_nsa;
           
            memset(&nr5g_nsa, 0, sizeof(nr5g_nsa));
            err = at_tok_scanf(p_cur->line, "%s%s%s%s%d%d%d%dd",
                NULL, NULL, &nr5g_nsa.mcc, &nr5g_nsa.mnc, &nr5g_nsa.pcid, &nr5g_nsa.rsrp, &nr5g_nsa.sinr, &nr5g_nsa.rsrq);
            if (err == 8 && verbose)
            {
                dbg_time("mcc=%s, mnc=%s, pcid=%d, rsrp=%d, sinr=%d, rsrq=%d",
                    nr5g_nsa.mcc, nr5g_nsa.mnc, nr5g_nsa.pcid, nr5g_nsa.rsrp, nr5g_nsa.sinr, nr5g_nsa.rsrq);
            }

            is_nr5g_nsa = 1;
        }
        else if (!strcmp(rat, "LTE"))
        {
            //+QENG: "LTE",<is_tdd>,<MCC>,<MNC>,<cellID>,<PCID>,<earfcn>,<freq_band_ind>,<UL_bandwidth>,<DL_bandwidth>,<TAC>,<RSRP>,<RSRQ>,<RSSI>,<SINR>,<CQI>,<tx_power>,<srxlev>
            struct qeng_servingcell_lte {
                char *is_tdd, *mcc, *mnc;
                int cellID/*hex*/, pcid, earfcn, freq_band_ind, ul_bandwidth, dl_bandwidth;
                int tac/*hex*/, rsrp, rsrq, rssi, sinr, cqi,tx_power,srxlev;
            };
            struct qeng_servingcell_lte lte;

            memset(&lte, 0, sizeof(lte));
            if (!strcmp(rat, state))
                err = at_tok_scanf(p_cur->line, "%s%s%s%s%s%x%d%d%d%d%d%x%d%d%d%d%d%d%d",
                    NULL, NULL, &lte.is_tdd, &lte.mcc, &lte.mnc,
                    &lte.cellID, &lte.pcid, &lte.earfcn, &lte.freq_band_ind, &lte.ul_bandwidth, &lte.dl_bandwidth,
                    &lte.tac, &lte.rsrp, &lte.rsrq, &lte.rssi, &lte.sinr, &lte.cqi, &lte.tx_power, &lte.srxlev);
            else
                 err = at_tok_scanf(p_cur->line, "%s%s%s%s%s%s%x%d%d%d%d%d%x%d%d%d%d%d%d%d",
                    NULL, NULL, NULL, &lte.is_tdd, &lte.mcc, &lte.mnc,
                    &lte.cellID, &lte.pcid, &lte.earfcn, &lte.freq_band_ind, &lte.ul_bandwidth, &lte.dl_bandwidth,
                    &lte.tac, &lte.rsrp, &lte.rsrq, &lte.rssi, &lte.sinr, &lte.cqi, &lte.tx_power, &lte.srxlev);
            
                if (err >= 18 && verbose)
                {
                    dbg_time("is_tdd=%s, mcc=%s, mnc=%s", lte.is_tdd, lte.mcc, lte.mnc);
                    dbg_time("cellID=%x, pcid=%d, earfcn=%d", lte.cellID, lte.pcid, lte.earfcn);
                    dbg_time("freq_band_ind=%d, ul_bandwidth=%d, dl_bandwidth=%d", lte.freq_band_ind, lte.ul_bandwidth, lte.dl_bandwidth);
                    dbg_time("tac=%x, rsrp=%d, rsrq=%d, rssi=%d, sinr=%d", lte.tac, lte.rsrp, lte.rsrq, lte.rssi, lte.sinr);
                    dbg_time("cqi=%d, tx_power=%d, earfcn=%d", lte.cqi, lte.tx_power, lte.srxlev);
                }
        }
    }

    if (is_nr5g_nsa) {
        int endc_avl, plmn_info_list_r15_avl, endc_rstr, nr5g_basic;

        is_nr5g_nsa = 0;
        safe_at_response_free(p_response);
        err = at_send_command_multiline("at+qendc", "+QENDC:", &p_response);
        if (at_response_error(err, p_response)) goto _error;
        if (!p_response->p_intermediates || !p_response->p_intermediates->line) goto _error;

        err = at_tok_scanf(p_response->p_intermediates->line, "%d%d%d%d",
            &endc_avl, &plmn_info_list_r15_avl, &endc_rstr, &nr5g_basic);
        if (err == 4 && nr5g_basic) {
            is_nr5g_nsa = 1;
        }
    }

    if (verbose)
        dbg_time("cops_act=%d, nr5g_nsa=%d, nr5g_sa=%d", cops_act, is_nr5g_nsa, nr5g_sa);

_error:
    safe_at_response_free(p_response);
    return retVal;
}

static int requestGetICCID(void) {
    int retVal = -1;
    int err;
    ATResponse *p_response = NULL;
    char *iccid;

    err = at_send_command_singleline("AT+QCCID", "+QCCID:", &p_response);
    if (at_response_error(err, p_response)) goto _error;
    if (!p_response->p_intermediates || !p_response->p_intermediates->line) goto _error;

    err = at_tok_scanf(p_response->p_intermediates->line, "%s", &iccid);
    if (err != 1) goto _error;

    if (iccid && iccid[0]) {
        dbg_time("%s %s", __func__, iccid);
        retVal = 0;
    }

_error:
    safe_at_response_free(p_response);
    return retVal;
}

static int requestGetIMSI(void) {
    int retVal = -1;
    int err;
    ATResponse *p_response = NULL;
    char *imsi;

    err = at_send_command_numeric("AT+CIMI", &p_response);
    if (at_response_error(err, p_response)) goto _error;
    if (!p_response->p_intermediates || !p_response->p_intermediates->line) goto _error;

    imsi = p_response->p_intermediates->line;
    if (imsi) {
        dbg_time("%s %s", __func__, imsi);
        retVal = 0;
    }

_error:
    safe_at_response_free(p_response);
    return retVal;
}

const struct request_ops atc_request_ops = {
    .requestBaseBandVersion = requestBaseBandVersion,
    .requestGetSIMStatus = requestGetSIMStatus,
    .requestEnterSimPin = requestEnterSimPin,
    .requestSetProfile = requestSetProfile,
    .requestGetProfile = requestGetProfile,
    .requestRegistrationState = requestRegistrationState,
    .requestSetupDataCall = requestSetupDataCall,
    .requestQueryDataCall = requestQueryDataCall,
    .requestDeactivateDefaultPDP = requestDeactivateDefaultPDP,
    .requestGetIPAddress = requestGetIPAddress,
    .requestGetSignalInfo = requestGetSignalInfo,
    .requestGetICCID = requestGetICCID,
    .requestGetIMSI = requestGetIMSI,
};
