/*
 * @Author: ybzhou yibo.zhou@lnxall.com
 * @Date: 2026-07-31 14:30:15
 * @LastEditors: ybzhou yibo.zhou@lnxall.com
 * @LastEditTime: 2026-08-31 16:28:06
 * @FilePath: /release-v2.10.x/proto_forward/src/frpc_proxy/frpc_cut.c
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#include <netinet/in.h>
#include <stdatomic.h>
#include <stdio.h>
#include <string.h>
#include <sys/epoll.h>
// #include "../common.h"
#include <bits/types/struct_timeval.h>
#include <pthread.h>
#include <sys/epoll.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/syslog.h>
#include <unistd.h>
#include <ctype.h>

#include "lz4.h"
#include "toml.h"
#include "iniparser.h"
#include "dictionary.h"

#include "rsa.h"
#include "frpc_cut.h"
#include "../cmdclient.h"
#include "../automatic_north/north_automatic.h"

static frpc_cut_t g_frpc_cut = {
    .ctl = CTL_NO,
    .file = NULL,
    .frpc_fun = NULL,
    .result = -1,
};

frpc_cut_t *get_frpc_cut(void){
    return &g_frpc_cut;
}

// 老版本 frpc 配置文件路径 "/etc/frpc.ini"

/* frps版本, 0.61.2  /etc/frpc.toml
 * frpc补丁包 https://edge.lnxall.com:50080/frpc-elf/frpc_upgrade-arm64.elf EMS1000系列 */



/** 国内FRP服务器
 * 控制台访问：

    https://edge.lnxall.com:57500/

    客户端连接：
    域名：edge.lnxall.com
    端口：57000

    密码：lnxall123!@#
*/
static frpc_cfgi_t g_in_def = {
    .addr = "edge.lnxall.com",
    .port = 57000,
    .method = "token",
    .token = "lnxall123!@#"
};

/** 海外FRP服务器
 * 控制台访问：

    https://emmscloud-eu.lnxall.com/forward

    客户端连接：
    域名：emu-eu.lnxall.com
    端口：57000

    密码：lnxall123!@#
*/
static frpc_cfgi_t g_out_def = {
    .addr = "emu-eu.lnxall.com",
    .port = 57000,
    .method = "token",
    .token = "lnxall123!@#"
};

static int check_dir_is_exist(const char *dir_path) {
    struct stat st;
    if (stat(dir_path, &st) == -1) {  // 检查目录是否存在
        ems_syslog(LOG_ERR, "directory not exists!!!");
        return -1;
    } 
    else {
        ems_syslog(LOG_DEBUG, "directory already exists");
    }
    return 0;
}

static void print_cfg(frpc_cfgi_t *cfg) {
    ems_syslog(LOG_NOTICE, "frpc  serverAddr  = %s", cfg->addr);
    ems_syslog(LOG_NOTICE, "frpc  serverPort  = %d", cfg->port);
    ems_syslog(LOG_NOTICE, "frpc  auth.method  = %s", cfg->method);
    ems_syslog(LOG_NOTICE, "frpc  auth.token  = %s", cfg->token);
}

//=================================== TOML API =====================================
static int read_frpc_cfg_toml(frpc_cfgi_t *cfg, const char *path, int origin) 
{
    if (!cfg || !path) return -1;

    int ret = 0; 
    FILE *fp;
    char errbuf[200];
    const char *toml_name = path;

    fp = fopen(toml_name, "r");
    if (!fp) {
        ems_syslog(LOG_ERR, "cannot open file for r: %s", toml_name);
        return -1;
    }

    toml_table_t *conf = toml_parse_file(fp, errbuf, sizeof(errbuf));
    fclose(fp);
    if (!conf) {
        ems_syslog(LOG_ERR, "cannot parse, reason: %s !!!", errbuf);
        return -1;
    }

    toml_datum_t serverAddr = toml_string_in(conf, "serverAddr");
    if (!serverAddr.ok) {
        ems_syslog(LOG_ERR, "cannot read serverAddr");
        ret = -1;
        goto end;
    }
    toml_datum_t serverPort = toml_int_in(conf, "serverPort");
    if (!serverPort.ok) {
        ems_syslog(LOG_ERR, "cannot read serverPort");
        ret = -1;
        goto end;
    }

    /* auth 子表 */
    toml_table_t *auth = toml_table_in(conf, "auth");
    if (!auth) {
        ems_syslog(LOG_ERR, "missing auth");
        ret = -1;
        goto end;
    }

    toml_datum_t method = toml_string_in(auth, "method");
    if (!method.ok) {
        ems_syslog(LOG_ERR, "cannot read auth.method");
        ret = -1;
        goto end;
    }

    toml_datum_t token = toml_string_in(auth, "token");
    if (!token.ok) {
        ems_syslog(LOG_ERR, "cannot read auth.token");
        ret = -1;
        goto end;
    }

    frpc_cfgi_t *frpc_def = origin ? &g_out_def : &g_in_def; // 默认配置

    snprintf(cfg->addr,   sizeof(cfg->addr),   "%s", serverAddr.u.s ? serverAddr.u.s : frpc_def->addr);
    cfg->port = (int)serverPort.u.i;
    snprintf(cfg->method, sizeof(cfg->method), "%s", method.u.s ? method.u.s : frpc_def->method);
    snprintf(cfg->token,  sizeof(cfg->token),  "%s", token.u.s ? token.u.s : frpc_def->token);
end:
    FREE_AND_NULL(serverAddr.u.s);
    FREE_AND_NULL(method.u.s);
    FREE_AND_NULL(token.u.s);

    toml_free(conf);
    return ret;
}

static int update_frpc_cfg_toml(frpc_cfgi_t *cfg, const char *path) 
{
    if (!cfg || !path) return -1;

    const char *toml_name = path;
    FILE *src = fopen(toml_name, "r");
    if (!src) {
        ems_syslog(LOG_ERR, "cannot open file for r: %s", toml_name);
        return -1;
    }

    char tmp_toml_name[128] = {0};
    snprintf(tmp_toml_name, sizeof(tmp_toml_name), "%s.%s", toml_name, "tmp");
    FILE *dst = fopen(tmp_toml_name, "w");
    if (!dst) {
        fclose(src);
        ems_syslog(LOG_ERR, "cannot open file for w: %s", tmp_toml_name);
        return -1;
    }

    char line[1024];
    while (fgets(line, sizeof(line), src)) {
        char *p = line;
        while (*p == ' ' || *p == '\t') p++;

        // 空行、注释行、section 行原样输出
        if (*p == '\0' || *p == '\n' || *p == '#' || *p == '[') {
            fputs(line, dst);
            continue;
        }

        if (strncmp(p, "serverAddr",  10) == 0 && (p[10] == ' ' || p[10] == '\t' || p[10] == '=')) {
            fprintf(dst, "%-30s = \"%s\"\n", "serverAddr", cfg->addr);
        }
        else if (strncmp(p, "serverPort",  10) == 0 && (p[10] == ' ' || p[10] == '\t' || p[10] == '=')) {
            fprintf(dst, "%-30s = %d\n", "serverPort", cfg->port);
        }
        else if (strncmp(p, "auth.method", 11) == 0 && (p[11] == ' ' || p[11] == '\t' || p[11] == '=')) {
            fprintf(dst, "%-30s = \"%s\"\n", "auth.method", cfg->method);
        }
        else if (strncmp(p, "auth.token",  10) == 0 && (p[10] == ' ' || p[10] == '\t' || p[10] == '=')) {
            fprintf(dst, "%-30s = \"%s\"\n", "auth.token", cfg->token);
        }
        else {
            /* 其余 key=value 行统一对齐 = */
            char *eq = strchr(p, '=');
            if (eq) {
                /* 提取 key，去掉尾部空格 */
                size_t key_len = (size_t)(eq - p);
                while (key_len > 0 && (p[key_len-1] == ' ' || p[key_len-1] == '\t'))
                    key_len--;
                char key[256];
                memcpy(key, p, key_len);
                key[key_len] = '\0';

                /* 提取 value，跳过首部空格，去掉尾部换行 */
                char *val = eq + 1;
                while (*val == ' ' || *val == '\t') val++;
                val[strcspn(val, "\r\n")] = '\0';

                fprintf(dst, "%-30s = %s\n", key, val);
            } else {
                fputs(line, dst);
            }
        }
    }
    fclose(src);
    fclose(dst);

    if (rename(tmp_toml_name, toml_name) != 0) {      // 原子替换：用临时文件覆盖原文件
        remove(tmp_toml_name);
        return -1;
    }
    return 0;
}

static frpc_fun_t frpc_fun_toml = {
    .read_frpc_cfg = read_frpc_cfg_toml,
    .update_frpc_cfg = update_frpc_cfg_toml
};

//=================================== INI API =====================================
static int save_ini_file(dictionary *dict, const char *ini_name){
    FILE *fp;

    fp = fopen(ini_name, "w");
    if (!fp) {
        ems_syslog(LOG_ERR, "cannot open file for writing: %s", ini_name);
        return -1;
    }

    iniparser_dump_ini(dict, fp);
    fclose(fp);
    ems_syslog(LOG_INFO, "Config saved to: %s", ini_name);
    return 0;
}

static int write_common_cfg(dictionary *dict, frpc_cfgi_t *cfg){
    int ret;
    char port_str[16];

    ret = iniparser_set(dict, "common", NULL);
    if (ret < 0) return -1;

    ret = iniparser_set(dict, "common:server_addr", cfg->addr);
    if (ret < 0) return -1;

    snprintf(port_str, sizeof(port_str), "%d", cfg->port);
    ret = iniparser_set(dict, "common:server_port", port_str);
    if (ret < 0) return -1;

    ret = iniparser_set(dict, "common:privilege_token", cfg->token);
    if (ret < 0) return -1;  

    ret = iniparser_set(dict, "common:token", cfg->token); // 国内老版切海外，需要补填token键
    if (ret < 0) return -1;  
    
    return 0;  
}

static int read_frpc_cfg_ini(frpc_cfgi_t *cfg, const char *path, int origin)
{
    if (!cfg || !path) return -1;

    dictionary *dict;
    const char *ini_name = path;

    dict = iniparser_load(ini_name);
    if (!dict) {
        ems_syslog(LOG_ERR, "cannot parse file: %s !!!", ini_name);
        return -1;
    }

    frpc_cfgi_t *frpc_def = origin ? &g_out_def : &g_in_def;
      
    snprintf(cfg->addr,   sizeof(cfg->addr),   "%s", iniparser_getstring(dict, "common:server_addr",      frpc_def->addr));
    cfg->port = iniparser_getint(dict, "common:server_port", frpc_def->port);
    snprintf(cfg->method, sizeof(cfg->method), "%s", iniparser_getstring(dict, "common:method",            frpc_def->method)); // ini无method
    snprintf(cfg->token,  sizeof(cfg->token),  "%s", iniparser_getstring(dict, "common:privilege_token",   frpc_def->token));
    
    iniparser_freedict(dict);
    return 0;
}

static int update_frpc_cfg_ini(frpc_cfgi_t *cfg, const char *path)
{
    if (!cfg || !path) return -1;

    int ret = 0;
    dictionary *dict;
    const char *ini_name = path;

    dict = iniparser_load(ini_name);
    if (!dict) {
        ems_syslog(LOG_ERR, "cannot parse file: %s", ini_name);
        return 1;
    }

    ret = write_common_cfg(dict, cfg);
    if (ret < 0) {
        iniparser_freedict(dict);
        return -1;
    }

    ret = save_ini_file(dict, ini_name); //

    iniparser_freedict(dict);
    return ret;
}

static frpc_fun_t frpc_fun_ini = {
    .read_frpc_cfg = read_frpc_cfg_ini,
    .update_frpc_cfg = update_frpc_cfg_ini
};

static int is_frpc_cfg_same(frpc_cfgi_t *a, frpc_cfgi_t *b)
{
    if (a->port != b->port)                     return 0;
    if (strcmp(a->addr,   b->addr)   != 0)      return 0;
    if (strcmp(a->method, b->method) != 0)      return 0;
    if (strcmp(a->token,  b->token)  != 0)      return 0;
    return 1;
}

static int frpc_cut_check(frpc_cut_t *frpc_cut)
{ 
    int ret = 0;
    frpc_fun_t *fun = frpc_cut->frpc_fun;

    // toml / ini
    frpc_cfgi_t cfg = {0};
    if (fun->read_frpc_cfg(&cfg, frpc_cut->file, frpc_cut->origin) != 0) {
        ems_syslog(LOG_ERR, "read frpc cfg from %s fail!!!", frpc_cut->file);
        return 0;
    }
    ems_syslog(LOG_INFO, "frpc -c %s", frpc_cut->file);
    print_cfg(&cfg);

    if (is_frpc_cfg_same(&cfg, frpc_cut->now)) {
        ems_syslog(LOG_INFO, "frpc cfg is already up-to-date");
    } 
    else {
        ems_syslog(LOG_NOTICE, "frpc cfg mismatch, updating..."); // 不一致，写回frpc配置文件 
        fun->update_frpc_cfg(frpc_cut->now, frpc_cut->file);
        ret = 1;
    }

    return ret;
}

//==============================================
static int frpc_is_toml(void)
{
    FILE *fp = popen("systemctl status frpc 2>/dev/null", "r");
    if (!fp) return 0;

    char line[512];
    int ret = 0;

    while (fgets(line, sizeof(line), fp)) {
        if (strstr(line, "frpc.toml")) {
            ret = 1;
            break;
        }
    }

    pclose(fp);
    return ret;
}

static int frpc_is_active(void) {
    FILE *fp;
    char buffer[256];
    
    fp = popen("systemctl is-active frpc", "r");
    if (fp == NULL) return -1;
    
    fgets(buffer, sizeof(buffer), fp);
    pclose(fp);
    buffer[strcspn(buffer, "\n")] = '\0';
    
    if (strcmp(buffer, "active") == 0) {
        return 0; // 正常运行
    }
    
    return -1;
}
#if 0
static int _check_file_exist(char *file_name) {
    
    char file_path[128] = "";	
	snprintf(file_path, sizeof(file_path), "%s/%s", FRPC_CFG_PATH, file_name);

    if (access(file_path, F_OK) != -1) {  // 检查文件是否存在
        // 文件存在，删除文件
        if (remove(file_path) == 0) {
            ems_syslog(LOG_NOTICE, "File deleted successfully");
        } else {
            ems_syslog(LOG_ERR, "Error deleting file");
            return -1;
        }
    } else {
        ems_syslog(LOG_NOTICE, "File does not exist");
    }

    return 0;
}
#endif
static int save_frpc_cfg(frpc_cut_t *frpc_cut, const char *cfg_file)
{
    cJSON *root = NULL;
	char *string = NULL;

    if (!frpc_cut || !cfg_file) {
        ems_syslog(LOG_ERR, "frpc_cut:%p cfg_file:%p", frpc_cut, cfg_file);
        return -1;
    }

	root = cJSON_CreateObject();
	if (root == NULL)
	{
		ems_syslog(LOG_NOTICE, "cJSON_CreateObject root NULL!!!");
		goto end;
	}

    cJSON_AddNumberToObject(root, "origin", frpc_cut->origin);
    
    frpc_cfgi_t *in  = &(frpc_cut->frpc_cfg[FRPC_IN]);
    cJSON *json_in = cJSON_CreateObject();
    if (!json_in){
        goto end;
    }
    cJSON_AddItemToObject(root, "in",  json_in);
    cJSON_AddStringToObject(json_in, "serverAddr", in->addr);
    cJSON_AddNumberToObject(json_in, "serverPort", in->port);
    cJSON_AddStringToObject(json_in, "method",     in->method);
    cJSON_AddStringToObject(json_in, "token",      in->token);
    
    frpc_cfgi_t *out = &(frpc_cut->frpc_cfg[FRPC_OUT]);
    cJSON *json_out = cJSON_CreateObject();
    if (!json_out) { 
        cJSON_Delete(json_in); // 释放内存
        goto end;
    }
    cJSON_AddItemToObject(root, "out", json_out);
    cJSON_AddStringToObject(json_out, "serverAddr", out->addr);
    cJSON_AddNumberToObject(json_out, "serverPort", out->port);
    cJSON_AddStringToObject(json_out, "method",     out->method);
    cJSON_AddStringToObject(json_out, "token",      out->token);

    string = cJSON_Print(root);
    // ems_syslog(LOG_INFO, "frpc cfg %s", string);

	if (string){
		//if (_check_file_exist(FRPC_CFG_FILE) == 0)
			write_file_data(cfg_file, string, strlen(string));
	}
	else{
		ems_syslog(LOG_ERR, "create frpc_cfg.json error!!!");
	}
    
    cJSON_Delete(root);
    FREE_AND_NULL(string);
    return 0;

end:
	if (root != NULL)
	{
		cJSON_Delete(root);
	}
	return -1;
}

static int analy_frpc_cfg(frpc_cut_t *frpc_cut, char *data)
{
    cJSON *root = NULL;
    int ret = 0;

    root = cJSON_Parse(data);
    if (!root)
    {
        ems_syslog(LOG_ERR, "parse cfg file error!!!");
        ret = -1;
        goto out;
    }

    cJSON *origin = cJSON_GetObjectItem(root, "origin");
    if (cJSON_IsNumber(origin)) {
        frpc_cut->origin = origin->valueint;
    }
    else{
        frpc_cut->origin = 0; // 国内源
    }

    // in 
    cJSON *in = cJSON_GetObjectItem(root, "in");
    if (in) {
        cJSON *addr = cJSON_GetObjectItem(in, "serverAddr");
        cJSON *port = cJSON_GetObjectItem(in, "serverPort");
        cJSON *method = cJSON_GetObjectItem(in, "method");
        cJSON *token = cJSON_GetObjectItem(in, "token");

        if ((!addr) || (!port) || (!method) || (!token)) {
            ret = -1;
            goto out;            
        }

        frpc_cfgi_t *in = &(frpc_cut->frpc_cfg[FRPC_IN]);
        if (cJSON_IsString(addr))   snprintf(in->addr, sizeof(in->addr), "%s", addr->valuestring);
        if (cJSON_IsNumber(port))   in->port = port->valueint;
        if (cJSON_IsString(method)) snprintf(in->method, sizeof(in->method), "%s", method->valuestring);
        if (cJSON_IsString(token))  snprintf(in->token, sizeof(in->token), "%s", token->valuestring);

        print_cfg(in);
    }

    // out
    cJSON *out = cJSON_GetObjectItem(root, "out");
    if (out) {
        cJSON *addr = cJSON_GetObjectItem(out, "serverAddr");
        cJSON *port = cJSON_GetObjectItem(out, "serverPort");
        cJSON *method = cJSON_GetObjectItem(out, "method");
        cJSON *token = cJSON_GetObjectItem(out, "token");

        if ((!addr) || (!port) || (!method) || (!token)) {
            ret = -1;
            goto out;            
        }

        frpc_cfgi_t *out = &(frpc_cut->frpc_cfg[FRPC_OUT]);
        if (cJSON_IsString(addr))   snprintf(out->addr, sizeof(out->addr), "%s", addr->valuestring);
        if (cJSON_IsNumber(port))   out->port = port->valueint;
        if (cJSON_IsString(method)) snprintf(out->method, sizeof(out->method), "%s", method->valuestring);
        if (cJSON_IsString(token))  snprintf(out->token, sizeof(out->token), "%s", token->valuestring);

        print_cfg(out);
    }

    if (frpc_cut->origin){
        frpc_cut->now = &(frpc_cut->frpc_cfg[FRPC_OUT]);
    }
    else{
        frpc_cut->now = &(frpc_cut->frpc_cfg[FRPC_IN]);
    }

out:
    cJSON_Delete(root);

    return ret;
}

static int load_frpc_cfg(frpc_cut_t *frpc_cut, const char *cfg_file)
{
    char *data = NULL;
    int ret = 0;

    if (frpc_cut == NULL || cfg_file == NULL)
    {
        ems_syslog(LOG_ERR, "frpc_cut:%p cfg_file:%p", frpc_cut, cfg_file);
        return -1;
    }

    data = read_file_data(cfg_file);
    if (data == NULL)
    {
        ems_syslog(LOG_ERR, "read cfg file %s error", cfg_file);
        ret = -1;
        goto out;
    }
	ems_syslog(LOG_INFO, "raw_data: %s", data);

    ret = analy_frpc_cfg(frpc_cut, data);
    if (ret != 0){
        ems_syslog(LOG_WARNING, "analy frpc cfg fail!!!");
    }

out:
    if (data != NULL){
        free(data);
    }

    return ret;
}

// mqtt / opc-ua接口
static int _publish_message(mqtt_emms2_var_t* var, char* pub_topic, char* data, int data_len)
{
    if (var->base_param.disable_lz4)
    {
        return mqtt_session_publish(var->session, pub_topic, data, data_len);
    }
    else
    {
        int   ret             = 0;
        int   src_size        = data_len + 1;
        int   max_dst_size    = LZ4_compressBound(src_size);
        char* compressed_data = calloc(1, max_dst_size);

        const int compressed_data_size = LZ4_compress_default(data, compressed_data, src_size, max_dst_size);
        if (compressed_data_size <= 0)
        {
            ems_syslog(LOG_ERR, "LZ4_compress err");
            free(compressed_data);
            return -1;
        }
        // printf("We successfully compressed some data! Ratio: %.2f\n",
        //     (float) compressed_data_size/src_size);
        char topic_lz4[256] = {0};
        sprintf(topic_lz4, "%s/lz4/%d", pub_topic, data_len + 1);
        ems_syslog(LOG_INFO, "lz4 change topic [%s]", topic_lz4);
        ret = mqtt_session_publish(var->session, topic_lz4, compressed_data, compressed_data_size);
        free(compressed_data);
        return ret;
    }
}

static int mqtt_publish_frpc_result(mqtt_emms2_var_t* var, char* type, char* func_id, int seq, int result, char* info_key, cJSON* info)
{
    int   ret     = -1;
    char* topic   = malloc(256);
    char* payload = malloc(2048);
    if (topic && payload)
    {
        snprintf(topic, 256, "emms2/%s/%s/%s", type, var->topic_sn, func_id);
        int len = snprintf(payload,
                           1024,
                           "{\"funcId\":\"%s\",\"lcSN\": \"%s\",\"seq\": %d,\"time\":%lu,\"result\": %d",
                           func_id,
                           var->topic_sn,
                           seq,
                           time(NULL),
                           result);
        if (info_key && info)
        {
            char* i = cJSON_Print(info);
            if (i)
            {
                len += snprintf(payload + len, 1024 - len, ",\"%s\":%s", info_key, i);
                free(i);
            }
        }
        len += snprintf(payload + len, 1024 - len, "}");

        ret = _publish_message(var, topic, payload, len);
    }
    if (topic) free(topic);
    if (payload) free(payload);
    return ret;
}

static int mqtt_frpc_publish(frpc_cut_t *frpc_cut)
{
    if (frpc_cut->var == NULL) return -1;
        
    frpc_cfgi_t *now = frpc_cut->now;
    mqtt_emms2_var_t *var = (mqtt_emms2_var_t*)frpc_cut->var;
    {
        cJSON* _info = cJSON_CreateObject();

        cJSON* frpc = cJSON_CreateObject();
        cJSON_AddItemToObject(frpc, "serverAddr", cJSON_CreateString(now->addr));
        cJSON_AddNumberToObject(frpc, "serverPort", now->port);
        char* i = cJSON_PrintUnformatted(frpc);
        // rsa加密
        rsa_encrypt_b64(i, strlen(i));
        cJSON_AddItemToObject(_info, "frpc", cJSON_CreateString(frpc_cut->sen_buf));

        if (mqtt_publish_frpc_result(var, FUNCTION_TYPE_POST, FUNCTION_ID_FPRCRESULT, frpc_cut->seq, frpc_cut->result, "info", _info) < 0)
        {
            ems_syslog(LOG_ERR, "mqtt publish message failed!!!");
        }
        cJSON_Delete(_info);
        if (i) free(i);
    }
    return 0;
}

static int refresh_frpc_info(const cJSON* info, frpc_cut_t *frpc_cut)
{
    if (info == NULL || frpc_cut == NULL) return -1;

    cJSON *origin = cJSON_GetObjectItem(info, "origin");
    if (!cJSON_IsNumber(origin)) {
        ems_syslog(LOG_ERR, "info.origin invalid");
        return -1;
    }
    int region = origin->valueint ? FRPC_OUT : FRPC_IN;    

    cJSON *frpc = cJSON_GetObjectItem(info, "frpc");
    if (!frpc) {
        ems_syslog(LOG_ERR, "info.frpc not found");
        return -1;
    }

    // rsa解密
    snprintf(frpc_cut->rev_buf, sizeof(frpc_cut->rev_buf), "%s", frpc->valuestring);
    char json_str[MAX_BUF] = {0};
    if (rsa_decrypt_b64(json_str, sizeof(json_str)) == EXIT_FAILURE){
        ems_syslog(LOG_ERR, "rsa decrypt b64 error!!!");
        return -1;
    }

    cJSON *_frpc = NULL;
    _frpc = cJSON_Parse(json_str); // json解析
    if (!_frpc)
    {
        ems_syslog(LOG_ERR, "parse cfg file error!!!");
        return -1;
    }

    cJSON *addr   = cJSON_GetObjectItem(_frpc, "serverAddr");
    cJSON *port   = cJSON_GetObjectItem(_frpc, "serverPort");
    cJSON *method = cJSON_GetObjectItem(_frpc, "method");
    cJSON *token  = cJSON_GetObjectItem(_frpc, "token");
    
    if (!cJSON_IsString(addr) || !cJSON_IsNumber(port) || !cJSON_IsString(method) || !cJSON_IsString(token)) {
        ems_syslog(LOG_ERR, "info.frpc fields invalid!!!");
        cJSON_Delete(_frpc);
        return -1;
    }

    frpc_cfgi_t *cfg = &(frpc_cut->frpc_cfg[region]);
    snprintf(cfg->addr,   sizeof(cfg->addr),   "%s", addr->valuestring);
    cfg->port = port->valueint;
    if (frpc_cut->is_toml)
        snprintf(cfg->method, sizeof(cfg->method), "%s", method->valuestring);
    snprintf(cfg->token,  sizeof(cfg->token),  "%s", token->valuestring);

    frpc_cut->now    = cfg;
    frpc_cut->origin = origin->valueint; // 
    frpc_cut->ctl    = CTL_MQTT;

    atomic_store(&(frpc_cut->mark), 1);
    cJSON_Delete(_frpc);
    return 0;
}

int mqtt_frpc_cut_cmd(mqtt_emms2_var_t* var, cJSON* root, int seq) {

    mqtt_publish_frpc_result(var, FUNCTION_TYPE_SETRESP, FUNCTION_ID_FPRCUPDATE, seq, 0, NULL, NULL); // ACK

    if (var == NULL || root == NULL) {
        ems_syslog(LOG_ERR, "Invalid arguments!!!");
        return -1;
    }

    frpc_cut_t *frpc_cut = get_frpc_cut();
    cJSON* _seq     = cJSON_GetObjectItem(root, "seq");
    cJSON* info     = cJSON_GetObjectItem(root, "info");
    if (_seq == NULL || info == NULL || frpc_cut->frpc_fun == NULL || frpc_cut->now == NULL) { // frpc_fun == NULL，不支持frpc_cut 
        ems_syslog(LOG_ERR, "missing required fields in JSON!!!");
        return -1;
    }
    frpc_cut->seq = _seq->valueint;
    frpc_cut->var = var; // emms2云信息

    refresh_frpc_info(info, frpc_cut); //
    return 0;
}

// 待补充UA接口, 考虑到安全性屏控不支持，后续不再设计
int ua_frpc_cut_cmd(int seq){


    return 0;
    
}


static void* frpc_cut_task(void* arg)
{
    pthread_detach(pthread_self());

    frpc_cut_t *frpc_cut = (frpc_cut_t *)arg;
    int is_toml = frpc_cut->is_toml;
    // time_t now;
    // time_t last_check_tim = 0;

    while (1) {
        sleep(1);
        // now = time(NULL);

        // if (check_timeout_second(now, &last_check_tim, 60) || atomic_load(&(frpc_cut->mark)) == 1)
        if (atomic_load(&(frpc_cut->mark)) == 1) // 取消周期检查，避免frp功能与emsd耦合
        {
#if 0
            if (atomic_load(&frpc_cut->mark) == 1) {
                atomic_store(&(frpc_cut->mark), 0);                    
                save_frpc_cfg(frpc_cut, FRPC_CFG_PATH"/"FRPC_CFG_FILE);  // 持久化到 json
                ems_syslog(LOG_NOTICE, "frpc mark update done, saved to json");
            }
#endif
            atomic_store(&(frpc_cut->mark), 0);                    
            save_frpc_cfg(frpc_cut, FRPC_CFG_PATH"/"FRPC_CFG_FILE);  // 持久化到 json
            ems_syslog(LOG_NOTICE, "frpc mark update done, saved to json");

            if (frpc_cut_check(frpc_cut) == 1){
                cmd_call("systemctl restart frpc", -1, NULL, 0);
                ems_syslog(LOG_WARNING, "restart frpc succeed (%s)", is_toml ? "toml" : "ini");

                sleep(2);
                frpc_cut->result = frpc_is_active();                     // 检查frpc是否活跃
                ems_syslog(LOG_NOTICE, "check frpc active(%d)", frpc_cut->result);
            }
            else{
                ems_syslog(LOG_NOTICE, "check same...");
                frpc_cut->result = 0;
            }
            // 
            if (frpc_cut->ctl == CTL_MQTT){
                mqtt_frpc_publish(frpc_cut); 
            }
            else if (frpc_cut->ctl == CTL_UA){
                // what to do?
            } 
            frpc_cut->ctl = CTL_NO;
        }
    }
    return NULL;
}

int frpc_cut_fun(void)
{
    int is_toml = 0;
    frpc_cut_t *frpc_cut = get_frpc_cut();
    
    if (check_dir_is_exist(FRPC_CFG_PATH) == -1) { // 旧版本此目录不存在（RSA加密-私钥、公钥），不支持frpc切换
        goto out;
    }
    sleep(3);

    is_toml = frpc_is_toml();
    if (is_toml) {
        frpc_cut->frpc_fun = &frpc_fun_toml; // toml
        frpc_cut->file = strdup(FRPC_TOML);
    }
    else {
        frpc_cut->frpc_fun = &frpc_fun_ini;  // ini
        frpc_cut->file = strdup(FRPC_INI);
    }
    ems_syslog(LOG_INFO, "frpc cfg is %d (0 ini, 1 toml)", is_toml);
    frpc_cut->is_toml = is_toml;

    if (load_frpc_cfg(frpc_cut, FRPC_CFG_PATH"/"FRPC_CFG_FILE) == -1) {
        ems_syslog(LOG_ERR, "load frpc cfg fail!!!");
        goto out;
    }

    int ret = pthread_create(&frpc_cut->tid, NULL, frpc_cut_task, frpc_cut);
    if (ret != 0) {
        ems_syslog(LOG_ERR, "Failed to create frpc_cut_task thread: %s", strerror(ret));
        goto out;
    }
    else{
        pthread_setname_np(frpc_cut->tid, "frpc_cut_task");
    }
    return 0;

out:
    return -1;
}
