  /*
  * policy.c
  * Copyright (C) Aitor Cuadrado Zubizarreta <aitor@genuen.org>
  *
  * 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.
  */

#include <stdint.h>
#include <stdbool.h>
#include <syslog.h>
#include <unistd.h>
#include <signal.h>
#include <getopt.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>   // waitpid
#include <libgen.h>     // dirname() and basename()
#include <ctype.h>      // isdigit()
#include <errno.h>
#include <spawn.h>
#include <sys/file.h>   // flock
#include <arpa/inet.h>   // inet_ntop e inet_pton
#include <netinet/in.h>  // sockaddr_in and AF_INET
#include <ifaddrs.h>     // getifaddrs
#include <net/if.h>      // interface consts such as IFNAMSIZ
#include <sys/un.h>

#include <json.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/json_script.h>

#include <simple-netaid/ipaddr.h>
#include <simple-netaid/iproute.h>
#include <simple-netaid/interfaces.h>
#include <simple-netaid/helpers.h>
#include <simple-netaid/ethlink.h>

#include "def.h"
#include "config.h"
#include "policy.h"
#include "notify.h"
#include "util.h"
#include "netlink_monitor.h"
#include "iwlist.h"

#define WIFI_DIR "/etc/network/wifi"
#define PREFIX "/tmp/simple-netaid_"

#define PTY_MAX_LEN 16

#define strdup_or_null( str )  (str) != NULL ? strdup(str) : NULL

#ifndef container_of
#define container_of(ptr, type, member) \
    ((type *)((char *)(ptr) - offsetof(type, member)))
#endif

static struct blob_buf bb_t;

/* Enum for EGI policy order */
enum {
	STATUS_ID,
	STATUS_IFNAME,
	__STATUS_MAX,
};

/* Ubus Policy */
const struct blobmsg_policy status_policy[] =
{
	[STATUS_ID]  = { .name="id", .type=BLOBMSG_TYPE_INT32 },
	[STATUS_IFNAME] = { .name="ifname", .type=BLOBMSG_TYPE_STRING },
};

/* Enum for EGI policy order */
enum {
	DISCONNECT_ID,
	__DISCONNECT_MAX,
};

/* Ubus Policy */
const struct blobmsg_policy disconnect_policy[] =
{
	[DISCONNECT_ID] = { .name="id", .type=BLOBMSG_TYPE_INT32 },
};

/* Enum for EGI policy order */
enum {
	WIRED_CONNECTION_ID,
	WIRED_CONNECTION_IFNAME,
	__WIRED_CONNECTION_MAX,
};

/* Ubus Policy */
const struct blobmsg_policy wired_connection_policy[] =
{
	[WIRED_CONNECTION_ID]  = { .name="id", .type=BLOBMSG_TYPE_INT32 },
	[WIRED_CONNECTION_IFNAME] = { .name="ifname", .type=BLOBMSG_TYPE_STRING },
};

/* Enum for EGI policy order */
enum {
	WIRELESS_CONNECTION_ID,
	WIRELESS_CONNECTION_IFNAME,
	WIRELESS_CONNECTION_ESSID,
	WIRELESS_CONNECTION_PASSWD,
	WIRELESS_CONNECTION_FILENAME,
	__WIRELESS_CONNECTION_MAX,
};

/* Ubus Policy */
const struct blobmsg_policy wireless_connection_policy[] =
{
	[WIRELESS_CONNECTION_ID]  = { .name="id", .type=BLOBMSG_TYPE_INT32 },
	[WIRELESS_CONNECTION_IFNAME] = { .name="ifname", .type=BLOBMSG_TYPE_STRING },
	[WIRELESS_CONNECTION_ESSID] = { .name="essid", .type=BLOBMSG_TYPE_STRING },
	[WIRELESS_CONNECTION_PASSWD] = { .name="passwd", .type=BLOBMSG_TYPE_STRING },
	[WIRELESS_CONNECTION_FILENAME] = { .name="filename", .type=BLOBMSG_TYPE_STRING },
};

/* Enum for EGI policy order */
enum {
	UNINSTALL_SAVED_WIFI_ID,
	UNINSTALL_SAVED_WIFI_FILENAME,
	__UNINSTALL_SAVED_WIFI_MAX,
};

/* Ubus Policy */
const struct blobmsg_policy uninstall_saved_wifi_policy[] =
{
	[UNINSTALL_SAVED_WIFI_ID]  = { .name="id", .type=BLOBMSG_TYPE_INT32 },
	[UNINSTALL_SAVED_WIFI_FILENAME] = { .name="filename", .type=BLOBMSG_TYPE_STRING },
};

/* Enum for EGI policy order */
enum {
	SCAN_ACTIVE_WIFIS_ID,
	SCAN_ACTIVE_WIFIS_IFNAME,
	SCAN_ACTIVE_WIFIS_FORMAT,
	__SCAN_ACTIVE_WIFIS_MAX,
};

/* Ubus Policy */
const struct blobmsg_policy scan_active_wifis_policy[] =
{
	[SCAN_ACTIVE_WIFIS_ID]  = { .name="id", .type=BLOBMSG_TYPE_INT32 },
	[SCAN_ACTIVE_WIFIS_IFNAME] = { .name="ifname", .type=BLOBMSG_TYPE_STRING },
	[SCAN_ACTIVE_WIFIS_FORMAT]  = { .name="format", .type=BLOBMSG_TYPE_INT32 },
};


/* Ubus Methods */
const struct ubus_method netaid_methods[] =
{
	UBUS_METHOD("status", status_handler, status_policy),
    UBUS_METHOD("disconnect", disconnect_handler, disconnect_policy),
	UBUS_METHOD("wired_connection", wired_connection_handler, wired_connection_policy),
	UBUS_METHOD("wireless_connection", wireless_connection_handler, wireless_connection_policy),
	UBUS_METHOD("uninstall_saved_wifi", uninstall_saved_wifi_handler, uninstall_saved_wifi_policy),
	UBUS_METHOD("scan_active_wifis", scan_active_wifis_handler, scan_active_wifis_policy),
};

/* Ubus object type */
struct ubus_object_type netaid_obj_type =
	UBUS_OBJECT_TYPE("ering.netaid", netaid_methods);

/* Ubus object */
struct ubus_object netaid_obj=
{
	.name = "ering.netaid", 	/* with APP name */
	.type = &netaid_obj_type,
	.methods = netaid_methods,
	.n_methods = ARRAY_SIZE(netaid_methods),
	//.path= /* useless */
};

struct network_task {
    struct uloop_process proc;
    struct uloop_process ifup_proc;
    struct ubus_request_data req;
    struct ubus_context *ctx;
    struct uloop_timeout timeout;
    int fd_lock;
    char ifname[32];
    char path[1024];
    int retries;
    bool req_completed;
    bool dead;
    bool hotplug;
    pid_t wpa_pid;
};

static struct network_task *active_watchdog = NULL;
static struct network_task *hotplug_connect_task = NULL;

/* Forward declarations */
static void ifup_cb(struct uloop_timeout *t);
static void carrier_wait_cb(struct uloop_timeout *t);
static void send_reply_and_cleanup(struct network_task *task, const char *msg, int rc);
static bool path_allowed_for_wifi_config(const char *path);
static void network_task_release(struct network_task *task, bool reply_ubus,
        const char *msg, int rc);
static void cancel_active_task(const char *reply_msg);
static void cancel_inflight_hotplug(const char *ifname);

/**
 * @brief Struct for capturing detailed status
 */
struct iface_info {
    char type[32];
    char state[16];
    char ip4[INET_ADDRSTRLEN];
    char ip6[INET6_ADDRSTRLEN];
    bool is_bridge_port;
};

static bool path_allowed_for_wifi_config(const char *path)
{
    if (!path || !path[0] || strstr(path, ".."))
        return false;

    return (strncmp(path, WIFI_DIR, strlen(WIFI_DIR)) == 0 ||
            strncmp(path, PREFIX, strlen(PREFIX)) == 0);
}

static bool path_is_saved_wifi_store(const char *path)
{
    size_t dirlen = strlen(WIFI_DIR);

    if (!path || !path[0])
        return false;

    if (strncmp(path, WIFI_DIR, dirlen) != 0)
        return false;

    return path[dirlen] == '/' && path[dirlen + 1] != '\0';
}

/*
 * GTK "Connect" on a saved profile: filename set, essid/passwd empty.
 * Must never truncate /etc/network/wifi/* with the open-network template.
 */
static bool saved_wifi_profile_request(const char *filename, const char *path,
                                       const char *essid, const char *passwd)
{
    if (!filename || !filename[0])
        return false;
    if (!path_is_saved_wifi_store(path))
        return false;
    if (essid && essid[0] != '\0')
        return false;
    if (passwd && passwd[0] != '\0')
        return false;
    return true;
}

static void cancel_active_task(const char *reply_msg)
{
    struct network_task *task;

    if (!active_watchdog)
        return;

    task = active_watchdog;
    log_info("Canceling active network task on %s", task->ifname);

    uloop_timeout_cancel(&task->timeout);
    task->timeout.cb = NULL;

    if (task->proc.pid > 0) {
        uloop_process_delete(&task->proc);
        task->proc.cb = NULL;
        task->proc.pid = 0;
    }

    if (task->ifup_proc.pid > 0) {
        uloop_process_delete(&task->ifup_proc);
        task->ifup_proc.cb = NULL;
        kill(task->ifup_proc.pid, SIGTERM);
        kill(task->ifup_proc.pid, SIGKILL);
        task->ifup_proc.pid = 0;
    }

    kill_all_processes(task->ifname, NULL);

    if (task->wpa_pid > 0) {
        kill(task->wpa_pid, SIGTERM);
        kill(task->wpa_pid, SIGKILL);
        task->wpa_pid = 0;
    }

    network_task_release(task, reply_msg != NULL,
            reply_msg ? reply_msg : "Operation canceled", -1);
}

static void cancel_inflight_hotplug(const char *ifname)
{
    struct network_task *task = hotplug_connect_task;

    if (!task)
        return;

    if (ifname && ifname[0] && strcmp(task->ifname, ifname) != 0)
        return;

    hotplug_connect_task = NULL;
    task->dead = true;
    uloop_timeout_cancel(&task->timeout);
    task->timeout.cb = NULL;

    if (task->ifup_proc.pid > 0) {
        uloop_process_delete(&task->ifup_proc);
        kill(task->ifup_proc.pid, SIGTERM);
        kill(task->ifup_proc.pid, SIGKILL);
        task->ifup_proc.pid = 0;
        task->ifup_proc.cb = NULL;
    }

    netlink_hotplug_processing_end();
    network_task_release(task, false, NULL, -1);
}

static void cancel_previous_task(const char *ifname)
{
    (void)ifname;
    cancel_active_task("Operation canceled");
}

static int get_ifupdown2_detail(const char *ifname, struct iface_info *info)
{
    char cmd[256];
    char buffer[4096];
    FILE *fp = NULL;
    struct json_object *iface_obj = NULL;

    memset(info, 0, sizeof(*info));

    /* -r checks the current status in the ifupdown2 database */
    snprintf(cmd, sizeof(cmd), "/usr/bin/ifquery -o json -r %s 2>/dev/null", ifname);

    fp = popen(cmd, "r");
    if (!fp)
        return -1;

    /* We read the whole the JSON into a buffer */
    size_t n = fread(buffer, 1, sizeof(buffer) - 1, fp);
    buffer[n] = '\0';
    pclose(fp);

    if (n == 0)
        return -1;

    /* We use the standard function that is always present */
    struct json_object *parsed_json = json_tokener_parse(buffer);
    if (!parsed_json)
        return -1;

    if (json_object_get_type(parsed_json) == json_type_array) {
        iface_obj = json_object_array_get_idx(parsed_json, 0);
    } else {
        json_object_object_get_ex(parsed_json, ifname, &iface_obj);
    }

    /* ifupdown2 returns an object where the key is the name of the interface */
    if (iface_obj) {
        struct json_object *tmp;

        /* Get type (bridge, vlan, etc) */
        if (json_object_object_get_ex(iface_obj, "iftype", &tmp))
            strncpy(info->type, json_object_get_string(tmp), sizeof(info->type)-1);

        /* Get state (UP/DOWN) */
        if (json_object_object_get_ex(iface_obj, "ifstate", &tmp))
            strncpy(info->state, json_object_get_string(tmp), sizeof(info->state)-1);

        /* Get IPs (They are in an array called "address") */
        struct json_object *addr_array;
        if (json_object_object_get_ex(iface_obj, "address", &addr_array)) {
            int array_len = json_object_array_length(addr_array);
            for (int i = 0; i < array_len; i++) {
                const char *addr = json_object_get_string(json_object_array_get_idx(addr_array, i));
                if (strchr(addr, ':'))
                    /* It's IPv6 */
                    strncpy(info->ip6, addr, sizeof(info->ip6)-1);
                else
                    /* It's IPv4 */
                    strncpy(info->ip4, addr, sizeof(info->ip4)-1);
            }
        }

        /* Is it slave to a bridge? (Field "bridge_ports" or similar) */
        if (json_object_object_get_ex(iface_obj, "bridge-master", &tmp))
            info->is_bridge_port = true;
    }

    json_object_put(parsed_json);
    return 0;
}

static int ifquery_json_status(const char *ifname)
{
    char cmd[256];
    FILE *fp = NULL;
    char buffer[4096];
    int exists = 0;

    /* We use JSON to get structured data */
    /* If the interface does not exist in interfaces(5), ifupdown2 will return an error */
    snprintf(cmd, sizeof(cmd), "/usr/bin/ifquery -o json %s 2>/dev/null", ifname);

    fp = popen(cmd, "r");
    if (!fp)
        return -1;

    size_t len = fread(buffer, 1, sizeof(buffer) - 1, fp);
    buffer[len] = '\0';
    int status = pclose(fp);

    if (status != 0 || len == 0)
        /* not configured */
        return 0;

    struct json_object *parsed_json = json_tokener_parse(buffer);
    if (!parsed_json)
        return 0;

    /* ifupdown2 returns an array of objects. We search for content */
    if (json_object_get_type(parsed_json) == json_type_array) {
        if (json_object_array_length(parsed_json) > 0) {
            exists = 1;

            // Optional: we could extract "ifstate" or "address" here
            /*
            struct json_object *iface_obj = json_object_array_get_idx(parsed_json, 0);
            struct json_object *state;
            if (json_object_object_get_ex(iface_obj, "ifstate", &state)) {
                printf("Real state: %s\n", json_object_get_string(state));
            }
            */
        }
    }

    json_object_put(parsed_json);
    return exists;
}

static bool interface_has_ip(const char *target_ifname)
{
    struct ifaddrs *ifaddr, *ifa;
    bool found = false;

    if (getifaddrs(&ifaddr) == -1)
        return false;

    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr == NULL)
            continue;

        /* Check whether it is the correct interface and whether it has IPv4 or IPv6 Global */
        if (strcmp(ifa->ifa_name, target_ifname) == 0) {
            if (ifa->ifa_addr->sa_family == AF_INET) {
                found = true;
                break;
            }
            if (ifa->ifa_addr->sa_family == AF_INET6) {
                struct sockaddr_in6 *current_addr = (struct sockaddr_in6 *)ifa->ifa_addr;
                /* Avoid local IPs (fe80::) */
                if (!IN6_IS_ADDR_LINKLOCAL(&current_addr->sin6_addr)) {
                    found = true;
                    break;
                }
            }
        }
    }

    freeifaddrs(ifaddr);

    return found;
}

static void send_network_event(struct ubus_context *ctx, const char *ifname,
                               const char *msg, int status)
{
    struct blob_buf b = {0};
    blob_buf_init(&b, 0);

    blobmsg_add_string(&b, "interface", ifname);
    blobmsg_add_string(&b, "message", msg);
    blobmsg_add_u32(&b, "status", (uint32_t)status);
    blobmsg_add_string(&b, "type", "wireless");

    /* send event with the name "network.connection" */
    ubus_send_event(ctx, "network.connection", b.head);
    blob_buf_free(&b);
    netaid_ui_notify_schedule();
}

static void network_task_release(struct network_task *task, bool reply_ubus,
        const char *msg, int rc)
{
    if (!task || task->dead)
        return;

    task->dead = true;
    uloop_timeout_cancel(&task->timeout);
    task->timeout.cb = NULL;

    if (task->proc.pid > 0) {
        uloop_process_delete(&task->proc);
        task->proc.cb = NULL;
        task->proc.pid = 0;
    }

    if (task->ifup_proc.pid > 0) {
        uloop_process_delete(&task->ifup_proc);
        task->ifup_proc.cb = NULL;
        task->ifup_proc.pid = 0;
    }

    if (reply_ubus && !task->req_completed && task->ctx) {
        blob_buf_init(&bb_t, 0);
        blobmsg_add_string(&bb_t, "reply", msg ? msg : "");
        blobmsg_add_u32(&bb_t, "status", (uint32_t)rc);
        ubus_send_reply(task->ctx, &task->req, bb_t.head);
        ubus_complete_deferred_request(task->ctx, &task->req, UBUS_STATUS_OK);
        task->req_completed = true;
        if (msg && task->ifname[0])
            send_network_event(task->ctx, task->ifname, msg, rc);
    }

    if (task->path[0] && strncmp(task->path, PREFIX, strlen(PREFIX)) == 0)
        unlink(task->path);

    if (task->fd_lock >= 0) {
        flock(task->fd_lock, LOCK_UN);
        close(task->fd_lock);
        task->fd_lock = -1;
    }

    if (active_watchdog == task)
        active_watchdog = NULL;

    if (hotplug_connect_task == task)
        hotplug_connect_task = NULL;

    netlink_hotplug_connect_end(task->ifname);

    free(task);
}

static void send_reply_and_cleanup(struct network_task *task, const char *msg, int rc)
{
    if (task && task->hotplug && rc != 0) {
        char log_buf[256];

        snprintf(log_buf, sizeof(log_buf), "Configuration error for %s",
                 task->ifname);
        log_netlink_event(log_buf);
        netlink_hotplug_processing_end();
    }
    network_task_release(task, true, msg, rc);
}

static char *get_ip_interface(const char *target_ifname)
{
    struct ifaddrs *ifaddr, *ifa;
    static char ip_str[INET_ADDRSTRLEN];

    if (getifaddrs(&ifaddr) == -1)
        return NULL;

    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != AF_INET)
            continue;

        if (strcmp(ifa->ifa_name, target_ifname) == 0) {
            struct sockaddr_in *sa = (struct sockaddr_in *)ifa->ifa_addr;
            inet_ntop(AF_INET, &sa->sin_addr, ip_str, sizeof(ip_str));
            freeifaddrs(ifaddr);
            return ip_str;
        }
    }
    freeifaddrs(ifaddr);
    return NULL;
}

/* --- Watchdog: persistent monitor --- */
static void watchdog_cb(struct uloop_timeout *t)
{
    /* We retrieve the pointer to our task structure using container_of */
    struct network_task *task = container_of(t, struct network_task, timeout);
    char *ip = get_ip_interface(task->ifname);

    if (!ip || strcmp(ip, "0.0.0.0") == 0) {
        log_warn("netaid: Watchdog detected connection loss on %s", task->ifname);
        log_warn("netaid: Re-initializing %s", task->ifname);
        task->timeout.cb = ifup_cb; 
        uloop_timeout_set(&task->timeout, 1000);
        
        /* IMPORTANT: Remove from active_watchdog as it is now "connecting" */
        active_watchdog = NULL; 
        /*
        send_reply_and_cleanup(task, "Connection lost", -1);
        */
        return;
    }

    /* If everything is OK, we reschedule the watchdog (e.g., every 30 seconds) */
    uloop_timeout_set(&task->timeout, 30000);
}

static void check_ip_retry_cb(struct uloop_timeout *t)
{
    struct network_task *task = container_of(t, struct network_task, timeout);
    char reply[256];

    if (task->dead)
        return;

    /* Try to get the IP */
    char *ip = get_ip_interface(task->ifname);

    if (task->dead)
        return;

    if (ip && strcmp(ip, "0.0.0.0") != 0) {
        /* Succeed: IP obtained (ubus deferred request already completed in ifup_cb) */
        snprintf(reply, sizeof(reply), "Connected to %s with IP %s", task->ifname, ip);
        log_info("netaid: %s", reply);

        netlink_hotplug_user_connect_success(task->ifname);

        if (task->hotplug) {
            if (ip)
                snprintf(reply, sizeof(reply),
                         "Successfull connection: %s with IP %s",
                         task->ifname, ip);
            else
                snprintf(reply, sizeof(reply),
                         "Successfull connection: %s", task->ifname);
            log_netlink_event(reply);
            netlink_hotplug_processing_end();
        } else if (task->ctx && task->ifname[0]) {
            send_network_event(task->ctx, task->ifname, reply, 0);
        }

        active_watchdog = task;
        task->timeout.cb = watchdog_cb;
        uloop_timeout_set(&task->timeout, 30000);
        return;
    }

    /* If there is no IP, we retry until the counter runs out */
    if (task->retries > 0) {
        task->retries--;
        log_debug("netaid: Waiting for IP at %s (%d attempts remaining)",
                task->ifname, task->retries);
        uloop_timeout_set(&task->timeout, 1000);
    } else {
        /* Exhausted: definitive error */
        log_error("netaid: Timeout waiting for IP in %s", task->ifname);
        send_reply_and_cleanup(task,
                "Failed attempt: No IP address assigned after timeout", -1);
    }
}

static void final_stage_cb(struct uloop_process *proc, int ret)
{
    struct network_task *task = container_of(proc, struct network_task, ifup_proc);

    if (task->dead)
        return;

    task->ifup_proc.pid = 0;
    task->ifup_proc.cb = NULL;

    if (WIFEXITED(ret) && WEXITSTATUS(ret) == 0) {
        /* Instead of looking at the IP right now, we launch the retries */
        task->retries = 15; 
        task->timeout.cb = check_ip_retry_cb;
        uloop_timeout_set(&task->timeout, 1000); 
        return;
    }
    send_reply_and_cleanup(task, "ifup failed or timed out", -1);
}

static void ifup_cb(struct uloop_timeout *t)
{
    struct network_task *task = container_of(t, struct network_task, timeout);

    if (task->dead)
        return;

    pid_t ifup_pid;
    posix_spawn_file_actions_t actions;
    posix_spawnattr_t attr;
    sigset_t mask;
    int err;     
    /* Define a minimum PATH */
    char *env[] = { "PATH=/usr/sbin:/usr/bin:/sbin:/bin", NULL };
    char *const ifup_args[] = { "/usr/sbin/ifup", task->ifname, NULL };

    /* Redirection for the background */
    posix_spawn_file_actions_init(&actions);
    posix_spawn_file_actions_addopen(&actions, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
    posix_spawn_file_actions_addopen(&actions, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
    posix_spawn_file_actions_addopen(&actions, STDIN_FILENO, "/dev/null", O_RDONLY, 0);

    /* Clean signal mask */
    posix_spawnattr_init(&attr);
    sigemptyset(&mask);
    posix_spawnattr_setsigmask(&attr, &mask);
    posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK);

    err = posix_spawn(&ifup_pid, "/usr/sbin/ifup", &actions, &attr, ifup_args, env);
    
    if (err == 0) {
        task->ifup_proc.pid = ifup_pid;
        task->ifup_proc.cb = final_stage_cb;
        uloop_process_add(&task->ifup_proc);

        if (task->ctx && !task->req_completed) {
            struct blob_buf b = {};

            blob_buf_init(&b, 0);
            blobmsg_add_string(&b, "status", "starting");
            blobmsg_add_string(&b, "info",
                               "Interface is coming up, checking IP in background");
            ubus_send_reply(task->ctx, &task->req, b.head);
            ubus_complete_deferred_request(task->ctx, &task->req, UBUS_STATUS_OK);
            task->req_completed = true;
            blob_buf_free(&b);
        }
    } else {
        log_error("netaid: Failed to spawn ifup: %s", strerror(err));
        send_reply_and_cleanup(task, "ifup spawn failed", -1);
    }

    posix_spawn_file_actions_destroy(&actions);
    posix_spawnattr_destroy(&attr);
}

static void issue_network_event(struct ubus_context *ctx, const char *ifname, const char *action)
{
    blob_buf_init(&bb_t, 0);
    blobmsg_add_string(&bb_t, "interface", ifname);
    blobmsg_add_string(&bb_t, "action", action); // "connected", "disconnected", "link_lost"

    /* Send event to the system bus */
    ubus_send_event(ctx, "netaid.network", bb_t.head);
}

int status_handler(struct ubus_context *ctx, struct ubus_object *obj,
                   struct ubus_request_data *req, const char *method,
                   struct blob_attr *msg)
{
    struct blob_attr *tb[__STATUS_MAX];
    struct iface_info info;
    void *c;

    blobmsg_parse(status_policy, __STATUS_MAX, tb, blob_data(msg), blob_len(msg));

    if (!tb[STATUS_IFNAME])
        return UBUS_STATUS_INVALID_ARGUMENT;

    const char *ifname = blobmsg_get_string(tb[STATUS_IFNAME]);
    if (!ifname || *ifname == '\0' || strlen(ifname) >= IF_NAMESIZE)
        return UBUS_STATUS_INVALID_ARGUMENT;

    /* Query the oracle (ifupdown2 JSON) */
    if (get_ifupdown2_detail(ifname, &info) != 0)
        return UBUS_STATUS_NOT_FOUND;

    blob_buf_init(&bb_t, 0);
    blobmsg_add_string(&bb_t, "ifname", ifname);
    blobmsg_add_string(&bb_t, "type", info.type);
    blobmsg_add_string(&bb_t, "state", info.state);
    blobmsg_add_string(&bb_t, "ip4", info.ip4[0] ? info.ip4 : "none");
    blobmsg_add_string(&bb_t, "ip6", info.ip6[0] ? info.ip6 : "none");
    
    /* Add information about whether a connection process is in progress */
    bool busy = (active_watchdog && strcmp(active_watchdog->ifname, ifname) == 0);
    blobmsg_add_u8(&bb_t, "pending_task", busy);

    ubus_send_reply(ctx, req, bb_t.head);
    return UBUS_STATUS_OK;
}

/**
 * \brief Callback function for ubus disconnect method handling
 */
int disconnect_handler(struct ubus_context *ctx, struct ubus_object *obj,
                       struct ubus_request_data *req, const char *method,
                       struct blob_attr *msg_t)
{
    char reply_msg[128] = {0};
    const char *target_if = iproute();

    if (target_if && target_if[0] != '\0') {
        /* GTK/software disconnect: stay admin UP; block hotplug until reconnect. */
        netlink_hotplug_user_disconnect(target_if);
        cancel_previous_task(target_if);
        cancel_inflight_hotplug(target_if);
        wired_teardown(target_if, true);

        snprintf(reply_msg, sizeof(reply_msg), "Interface %s disconnected", target_if);
    } else {
        strncpy(reply_msg, "No active interface found", sizeof(reply_msg));
    }

    /* Send reply to the system bus (synchronous) */
    blob_buf_init(&bb_t, 0);
    blobmsg_add_string(&bb_t, "reply", reply_msg);
    ubus_send_reply(ctx, req, bb_t.head);

    netaid_ui_notify_schedule();

    return UBUS_STATUS_OK;
}

static int validate_interface(const char *ifname, struct iface_info *info)
{
    char bridge_path[256];
    struct stat st;

    if (!ifname || !info)
        return -1;

    /* Check if the folder /sys/class/net/IFNAME/bridge exists.
     * If it exists, this interface is part of a bridge */
    snprintf(bridge_path, sizeof(bridge_path), "/sys/class/net/%s/bridge", ifname);
    
    if (stat(bridge_path, &st) == 0) {
        info->is_bridge_port = true;
    } else {
        info->is_bridge_port = false;
    }

    return 0;
}

static int wired_interface_prepare(const char *ifname)
{
    struct iface_info info;

    if (validate_interface(ifname, &info) != 0)
        return -1;

    if (info.is_bridge_port)
        return -1;

    kill_all_processes(ifname, NULL);
    ipaddr_flush(ifname);

    if (ifquery_json_status(ifname)) {
        char *const down_args[] = { "/usr/sbin/ifdown", (char *)ifname, NULL };

        if (safe_execute("/dev/null", down_args) != 0) {
            char *const force_args[] = { "/usr/sbin/ifdown", "--force",
                                         (char *)ifname, NULL };
            safe_execute("/dev/null", force_args);
        }
    } else {
        interface_down(ifname);
    }

    interface_up(ifname);
    return 0;
}

static void hotplug_task_abort(struct network_task *task)
{
    char log_buf[256];

    snprintf(log_buf, sizeof(log_buf), "Configuration error for %s", task->ifname);
    log_netlink_event(log_buf);
    netlink_hotplug_processing_end();
    network_task_release(task, false, NULL, -1);
}

static void carrier_wait_cb(struct uloop_timeout *t)
{
    struct network_task *task = container_of(t, struct network_task, timeout);

    if (task->dead)
        return;

    if (ethlink(task->ifname) == 0) {
        if (task->retries > 0) {
            task->retries--;
            uloop_timeout_set(&task->timeout, 500);
            return;
        }
        hotplug_task_abort(task);
        return;
    }

    if (wired_interface_prepare(task->ifname) != 0) {
        hotplug_task_abort(task);
        return;
    }

    task->timeout.cb = ifup_cb;
    uloop_timeout_set(&task->timeout, 500);
}

int wired_connection_hotplug_start(const char *ifname)
{
    struct network_task *task;

    if (!ifname || !ifname[0])
        return -1;

    cancel_previous_task(ifname);
    cancel_inflight_hotplug(ifname);

    task = calloc(1, sizeof(*task));
    if (!task)
        return -1;

    task->hotplug = true;
    task->req_completed = true;
    task->fd_lock = -1;
    strncpy(task->ifname, ifname, sizeof(task->ifname) - 1);
    task->ifname[sizeof(task->ifname) - 1] = '\0';

    if (if_nametoindex(ifname) == 0) {
        free(task);
        return -1;
    }

    hotplug_connect_task = task;
    task->retries = 20;
    task->timeout.cb = carrier_wait_cb;
    uloop_timeout_set(&task->timeout, 500);

    return 0;
}

/**
 * \brief Callback function for ubus wired_connection method handling
 */
int wired_connection_handler(struct ubus_context *ctx, struct ubus_object *obj,
                            struct ubus_request_data *req, const char *method,
                            struct blob_attr *msg_t)
{
    struct network_task *task = NULL;
    struct iface_info info;

    struct blob_attr *tb[__WIRED_CONNECTION_MAX];
    blobmsg_parse(wired_connection_policy, ARRAY_SIZE(wired_connection_policy),
                  tb, blob_data(msg_t), blob_len(msg_t));

    /* Parse interface */
    if (!tb[WIRED_CONNECTION_IFNAME])
        return UBUS_STATUS_INVALID_ARGUMENT;

    const char *ifname = blobmsg_get_string(tb[WIRED_CONNECTION_IFNAME]);
    if (!ifname || *ifname == '\0' || strlen(ifname) >= IF_NAMESIZE)
        return UBUS_STATUS_INVALID_ARGUMENT;

    cancel_previous_task(ifname);

    /* Memory allocation for the generic task */
    task = calloc(1, sizeof(*task));
    if (!task)
        return UBUS_STATUS_UNKNOWN_ERROR;

    /* User-initiated connect (GTK): block hotplug race during ifup. */
    netlink_hotplug_connect_begin(ifname);

    ubus_defer_request(ctx, req, &task->req);
    task->ctx = ctx;
    task->fd_lock = -1;

    strncpy(task->ifname, ifname, sizeof(task->ifname) - 1);
    task->ifname[sizeof(task->ifname) - 1] = '\0';
    
    /* Existence validation (Avoid fake interface names) */
    if (if_nametoindex(ifname) == 0) {
        send_reply_and_cleanup(task, "Error: Interface does not exist", -1);
        return UBUS_STATUS_OK;
    }

    /* Physical validation (admin DOWN is OK; we raise the link below). */
    if (ethlink(ifname) == 0) {
        send_reply_and_cleanup(task, "Error: Cable not detected", -1);
        return UBUS_STATUS_OK;
    }

    /* IMPORTANT! We must fill in the ‘info’ structure before usage */
    if (validate_interface(ifname, &info) != 0) {
        send_reply_and_cleanup(task, "Error: Interface validation failed", -1);
        return UBUS_STATUS_OK;
    }

    if (info.is_bridge_port) {
        send_reply_and_cleanup(task, "Error: Interface blocked by a bridge", -1);
        return UBUS_STATUS_OK;
    }

    if (wired_interface_prepare(ifname) != 0) {
        send_reply_and_cleanup(task, "Error: Interface preparation failed", -1);
        return UBUS_STATUS_OK;
    }

    /* Direct jump to the IP Phase (DHCP/Static via ifup) */
    /* We use a low delay of 500 ms so that the carrier stabilizes */
    task->timeout.cb = ifup_cb;
    uloop_timeout_set(&task->timeout, 500);

    return UBUS_STATUS_OK;
}

/**
 * @brief Checks whether the interface is WiFi-enabled.
 * Plan A: ifupdown2 (JSON)
 * Plan B: sysfs/ip-link (direct Kernel)
 */
int validate_wifi_interface(const char *ifname, struct iface_info *info)
{
    char syspath[256];
    struct stat st;

    /* Plan A: Query the oracle (ifupdown2 JSON) */
    if (get_ifupdown2_detail(ifname, info) == 0)
        /* Succeed: We have detailed info */
        return 0;

    /*Plan B: The interface is not in ifupdown2. Query the Kernel */
    log_warn("snetaid: %s is not in ifupdown2, querying the kernel", ifname);

    snprintf(syspath, sizeof(syspath), "/sys/class/net/%s", ifname);

    if (stat(syspath, &st) == 0) {
        /* The interface physically exists in the system */
        strncpy(info->type, "physical", sizeof(info->type)-1);

        /* Check if it is a bridge port by looking to see
         * whether the master folder exists 'master' */
        snprintf(syspath, sizeof(syspath), "/sys/class/net/%s/master", ifname);
        if (stat(syspath, &st) == 0)
            info->is_bridge_port = true;
        return 0;
    }

    /* It does not exist in any way */
    return -1;
}

/**
 * @brief Check if the interface has physical WiFi capabilities
 */
static bool is_wireless_interface(const char *ifname)
{
    char path[256];
    struct stat st;

    /* 1st method: 'wireless' folder (Old extensions/Wext) */
    snprintf(path, sizeof(path), "/sys/class/net/%s/wireless", ifname);
    if (stat(path, &st) == 0)
        return true;

    /* 2nd method: 'phy80211' folder (Modern drivers nl80211) */
    snprintf(path, sizeof(path), "/sys/class/net/%s/phy80211", ifname);
    if (stat(path, &st) == 0)
        return true;

    return false;
}

static void wpa_process_cb(struct uloop_process *p, int ret)
{
    struct network_task *task = container_of(p, struct network_task, proc);

    if (task->dead)
        return;

    log_info("netaid: wpa_supplicant (pid %d) stopped with status %d", p->pid, ret);

    task->proc.pid = 0;
    task->proc.cb = NULL;
    if (task->wpa_pid == (pid_t)p->pid)
        task->wpa_pid = 0;

    if (task->req_completed) {
        log_info("netaid: WiFi connection lost on %s", task->ifname);
        if (task->ctx && task->ifname[0])
            send_network_event(task->ctx, task->ifname, "wpa_supplicant disconnected", 0);
        network_task_release(task, false, NULL, 0);
        return;
    }

    if (ret != 0)
        send_reply_and_cleanup(task, "error: wpa_supplicant process failed",
                WIFEXITED(ret) ? WEXITSTATUS(ret) : -1);
    else
        send_reply_and_cleanup(task, "wpa_supplicant disconnected", 0);
}

/**
 * \brief Callback function for ubus wireless_connection method handling
 */
int wireless_connection_handler(struct ubus_context *ctx, struct ubus_object *obj,
                               struct ubus_request_data *req, const char *method,
                               struct blob_attr *msg_t)
{
    int rc = -1;
    char path[1024] = {0};
    char reply_msg[256] = {0};
    int wstatus = 0;
    bool pass_filter_ok = false;
    pid_t pid;
    struct network_task *task = NULL;
    struct iface_info info;
    sigset_t mask;     
    /* Define a minimum PATH */
    char *env[] = { "PATH=/usr/sbin:/usr/bin:/sbin:/bin", NULL };

    struct blob_attr *tb[__WIRELESS_CONNECTION_MAX];
    blobmsg_parse(wireless_connection_policy,
                ARRAY_SIZE(wireless_connection_policy), tb, blob_data(msg_t), blob_len(msg_t));

    if (!tb[WIRELESS_CONNECTION_IFNAME] || !tb[WIRELESS_CONNECTION_ESSID])
        return UBUS_STATUS_INVALID_ARGUMENT;

    const char *ifname = blobmsg_get_string(tb[WIRELESS_CONNECTION_IFNAME]);
    const char *essid = blobmsg_get_string(tb[WIRELESS_CONNECTION_ESSID]);
    const char *passwd = tb[WIRELESS_CONNECTION_PASSWD] ?
            blobmsg_get_string(tb[WIRELESS_CONNECTION_PASSWD]) : "";
    const char *filename = tb[WIRELESS_CONNECTION_FILENAME] ?
            blobmsg_get_string(tb[WIRELESS_CONNECTION_FILENAME]) : NULL;
    
    if (!ifname || *ifname == '\0' || strlen(ifname) >= IF_NAMESIZE)
        return UBUS_STATUS_INVALID_ARGUMENT;

    cancel_previous_task(ifname);

    /* Memory allocation for the generic task */
    task = calloc(1, sizeof(*task));
    if (!task)
        return UBUS_STATUS_UNKNOWN_ERROR;

    /* Defer the request and save context */
    ubus_defer_request(ctx, req, &task->req);
    task->ctx = ctx;
    /* Initialize to avoid closing junk FDs */
    task->fd_lock = -1;

    const char *curr_ifname = iproute();
    if (curr_ifname && curr_ifname[0] != '\0') {
        send_reply_and_cleanup(task, "Already connected", -1);
        return UBUS_STATUS_OK;
    }

    if (strlen(essid) > 32) {
        send_reply_and_cleanup(task, "Error: SSID too long", -1);
        return UBUS_STATUS_OK;
    }

    /* Generate path and security filter */
    if (filename && filename[0] != '\0') {
        snprintf(path, sizeof(path), "%s", filename);
    } else {
        char temp_path[] = "/tmp/simple-netaid_XXXXXX";
        int fd_tmp = mkstemp(temp_path);
        if (fd_tmp == -1) {
            send_reply_and_cleanup(task, "Error: mkstemp failed", -1);
            return UBUS_STATUS_OK;
        }
        close(fd_tmp);
        strncpy(path, temp_path, sizeof(path));
    }

    pass_filter_ok = path_allowed_for_wifi_config(path);
    if (!pass_filter_ok) {
        send_reply_and_cleanup(task, "Error: Config path not allowed", -1);
        return UBUS_STATUS_OK;
    }

    /* Existence validation (Avoid fake interface names) */
    if (if_nametoindex(ifname) == 0) {
        send_reply_and_cleanup(task, "Error: Interface does not exist", -1);
        return UBUS_STATUS_OK;
    }

    /* IMPORTANT! We must fill in the ‘info’ structure before usage */
    /* Does the interface exist? (Plan A + Plan B) */
    if (validate_wifi_interface(ifname, &info) != 0) {
        send_reply_and_cleanup(task, "Error: Interface doesn't exist", -1);
        return UBUS_STATUS_OK;
    }

    if (!get_interface_status(ifname)) {
        send_reply_and_cleanup(task, "Error: interface is DOWN", -1);
        return UBUS_STATUS_OK;
    }

    /* Is it really a WiFi card? */
    if (!is_wireless_interface(ifname)) {
        send_reply_and_cleanup(task,
                    "Error: The interface does not support wireless capabilities", -1);
        return UBUS_STATUS_OK;
    }

    /* Is it stuck in a bridge? */
    if (info.is_bridge_port) {
        send_reply_and_cleanup(task, "Error: Interface stuck in a bridge", -1);
        return UBUS_STATUS_OK;
    }

    /* Attempt to block the configuration file */
    task->fd_lock = open(path, O_CREAT | O_RDWR, 0600);
    if (task->fd_lock < 0) {
        send_reply_and_cleanup(task, "Error: Cannot open config", -1);
        return UBUS_STATUS_OK;
    }

    // LOCK_EX = Exclusive lock
    // LOCK_NB = Do not block (if anyone else has it, it fails immediately)
    if (flock(task->fd_lock, LOCK_EX | LOCK_NB) < 0) {
        send_reply_and_cleanup(task, "Error: Lock failed (busy)", -1);
        return UBUS_STATUS_OK;
    }

    kill_all_processes(ifname, NULL);
    ipaddr_flush(ifname);
    interface_down(ifname);

     if (ifquery_json_status(ifname)) {
        /* We only attempt ifdown if ifupdown2 supports managing it */
        char *const down_args[] = { "/usr/sbin/ifdown", (char *)ifname, NULL };
        if (safe_execute("/dev/null", down_args) != 0) {
            char *const force_args[] = { "/usr/sbin/ifdown", "--force", (char *)ifname, NULL };
            safe_execute("/dev/null", force_args);
        }
    } else {
        /* If it is not in /etc/network/interfaces, we download it manually just in case */
        interface_down(ifname);
    }

    interface_up(ifname);

    if (saved_wifi_profile_request(filename, path, essid, passwd)) {
        if (access(path, R_OK) != 0) {
            send_reply_and_cleanup(task, "Error: saved config file not found", -1);
            return UBUS_STATUS_OK;
        }
        if (!is_wpa_conf_file(path)) {
            send_reply_and_cleanup(task, "Error: invalid saved WiFi config file", -1);
            return UBUS_STATUS_OK;
        }
        log_info("netaid: using saved WiFi config %s", path);
    } else if (!passwd || passwd[0] == '\0') {
        if (!essid || essid[0] == '\0') {
            send_reply_and_cleanup(task,
                    "Error: ESSID required without a saved config file", -1);
            return UBUS_STATUS_OK;
        }

        if (path_is_saved_wifi_store(path)) {
            send_reply_and_cleanup(task,
                    "Error: refusing to overwrite saved WiFi config file", -1);
            return UBUS_STATUS_OK;
        }

        int fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0600);
        if (fd >= 0) {
            char *safe_essid = escape_ssid(essid);

            if (safe_essid) {
                dprintf(fd, "network={\n\tssid=\"%s\"\n\tkey_mgmt=NONE\n}\n",
                        safe_essid);
                free(safe_essid);
            }
            if (fsync(fd) < 0)
                perror("fsync");
            close(fd);
        } else {
            send_reply_and_cleanup(task, "Error: open() write", -1);
            return UBUS_STATUS_OK;
        }
    } else {
        if (append_wpa_config(essid, passwd, path) != 0) {
            send_reply_and_cleanup(task, "Error: append_wpa_config", -1);
            return UBUS_STATUS_OK;
        }

        int fd_sync = open(path, O_RDONLY);

        if (fd_sync >= 0) {
            fsync(fd_sync);
            close(fd_sync);
        }

        chmod(path, 0600);
    }

    strncpy(task->ifname, ifname, sizeof(task->ifname) - 1);
    task->ifname[sizeof(task->ifname) - 1] = '\0';
    if (strlen(path) >= sizeof(task->path)) {
        send_reply_and_cleanup(task, "Error: Config path too long", -1);
        return UBUS_STATUS_OK;
    }
    strncpy(task->path, path, sizeof(task->path) - 1);
    task->path[sizeof(task->path) - 1] = '\0';

    posix_spawnattr_t attr;
    posix_spawnattr_init(&attr);

    /* Configure the child to reset its signal mask to the default */
    posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF);
    sigemptyset(&mask);
    posix_spawnattr_setsigmask(&attr, &mask);
    
    fcntl(task->fd_lock, F_SETFD, FD_CLOEXEC);
    
    posix_spawn_file_actions_t actions;
    posix_spawn_file_actions_init(&actions);
    posix_spawn_file_actions_addopen(&actions, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
    posix_spawn_file_actions_addopen(&actions, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
    posix_spawn_file_actions_addopen(&actions, STDIN_FILENO, "/dev/null", O_RDONLY, 0);

    char *const wpa_args[] = { "/usr/sbin/wpa_supplicant", "-i", 
                                task->ifname, "-c", task->path, NULL };
    rc = posix_spawn(&pid, wpa_args[0], &actions, &attr, wpa_args, env);
    posix_spawnattr_destroy(&attr);

    if (rc == 0) {
        task->wpa_pid = pid;
        task->proc.pid = pid;
        task->proc.cb = wpa_process_cb;
        uloop_process_add(&task->proc);

        task->timeout.cb = ifup_cb;
        uloop_timeout_set(&task->timeout, 2000);

        active_watchdog = task;
    } else {
        send_reply_and_cleanup(task, "Failed to spawn wpa_supplicant", -1);
    }

    posix_spawn_file_actions_destroy(&actions);

    if (task->fd_lock >= 0) {
        flock(task->fd_lock, LOCK_UN);
        close(task->fd_lock);
        task->fd_lock = -1;
    }

    return UBUS_STATUS_OK;
}

/**
 * \brief Callback function for ubus uninstall_saved_wifi method handling
 */
int uninstall_saved_wifi_handler(struct ubus_context *ctx, struct ubus_object *obj,
                                 struct ubus_request_data *req, const char *method,
                                 struct blob_attr *msg)
{
    struct blob_attr *tb[__UNINSTALL_SAVED_WIFI_MAX];
    char reply_msg[256] = {0};
    
    blobmsg_parse(uninstall_saved_wifi_policy,
                __UNINSTALL_SAVED_WIFI_MAX, tb, blob_data(msg), blob_len(msg));

    if (!tb[UNINSTALL_SAVED_WIFI_FILENAME])
        return UBUS_STATUS_INVALID_ARGUMENT;

    const char *filename = blobmsg_get_string(tb[UNINSTALL_SAVED_WIFI_FILENAME]);

    if (!filename || !filename[0])
        return UBUS_STATUS_INVALID_ARGUMENT;

    if (!path_allowed_for_wifi_config(filename) || !is_wpa_conf_file(filename))
        return UBUS_STATUS_PERMISSION_DENIED;

    if (active_watchdog && strcmp(active_watchdog->path, filename) == 0) {
        log_info("netaid: Stopping active connection before uninstalling %s", filename);
        cancel_active_task("Stopped for uninstall");
    }

    /* Physically delete the file */
    if (unlink(filename) == 0) {
        snprintf(reply_msg, sizeof(reply_msg),
                "Wifi configuration %s uninstalled successfully", filename);
        log_info("netaid: File %s deleted at the request of Ubus", filename);
    } else {
        snprintf(reply_msg, sizeof(reply_msg),
                "Error: Could not remove %s (%s)", filename, strerror(errno));
    }

    /* Send reply to the system bus (synchronous) */
    blob_buf_init(&bb_t, 0);
    blobmsg_add_string(&bb_t, "reply", reply_msg);
    ubus_send_reply(ctx, req, bb_t.head);

    return UBUS_STATUS_OK;
}

/**
 * \brief Callback function for ubus scan_active_wifis method handling
 */
int scan_active_wifis_handler(struct ubus_context *ctx, struct ubus_object *obj,
                              struct ubus_request_data *req, const char *method,
                              struct blob_attr *msg_t)
{
    struct blob_attr *tb[__SCAN_ACTIVE_WIFIS_MAX];
    struct blob_buf bb_t = {0};
    int count;

    blobmsg_parse(scan_active_wifis_policy,
                ARRAY_SIZE(scan_active_wifis_policy), tb, blob_data(msg_t), blob_len(msg_t));

    if (!tb[SCAN_ACTIVE_WIFIS_IFNAME])
        return UBUS_STATUS_INVALID_ARGUMENT;

    blob_buf_init(&bb_t, 0);

    count = iwlist(blobmsg_get_string(tb[SCAN_ACTIVE_WIFIS_IFNAME]), &bb_t);
    if (count < 0) {
        blob_buf_free(&bb_t);
        return UBUS_STATUS_UNKNOWN_ERROR;
    }

    ubus_send_reply(ctx, req, bb_t.head);
    blob_buf_free(&bb_t);

    return UBUS_STATUS_OK;
}
