Issue Summary: Pre-signed AWS S3 URL Downloads Successfully in Postman but Returns 403 Forbidden on nRF5340 Device

Here is a focused support ticket description for your actual issue:


Issue Summary: Pre-signed AWS S3 URL Downloads Successfully in Postman but Returns 403 Forbidden on nRF5340 Device#include <zephyr/net/mqtt.h>
#include <zephyr/net/socket.h>
#include <zephyr/logging/log.h>
#include<zephyr/data/json.h>
#include <modem/modem_info.h>
#include<zephyr/sys/reboot.h>
#include<zephyr/sys/util.h>
#include "aws_iot/aws-certs.h"
#include "iot_connections.h"
#include <stdio.h>
#include <net/fota_download.h>
#include <dfu/dfu_target_mcuboot.h>
#include <zephyr/net/sntp.h>
#include <dfu/dfu_target.h>
#include "time.h"
#include <zephyr/logging/log_ctrl.h>
#if defined(CONFIG_AWS_FOTA)
#include <net/aws_fota.h>
#include <zephyr/net/net_ip.h>

#endif

#if defined(CONFIG_AWS_IOT_PROVISION_CERTIFICATES)
#include CONFIG_AWS_IOT_CERTIFICATES_FILE
#endif
#define MY_DEVICE_IMEI CONFIG_DEVICE_IMEI
 atomic_t fota_active = ATOMIC_INIT(0);
LOG_MODULE_REGISTER(aws, LOG_LEVEL_DBG);
#define SNTP_SERVER "0.pool.ntp.org"
#define SEC_TAG_DMS        10
#define SEC_TAG_CATTLE     11
#if !defined(CONFIG_AWS_IOT_BROKER_HOST_NAME_APP)
BUILD_ASSERT(sizeof(CONFIG_AWS_IOT_BROKER_HOST_NAME) > 1,
			 "AWS IoT hostname not set");
BUILD_ASSERT(CONFIG_AWS_IOT_BROKER_HOST_NAME_MAX_LEN >=
				 sizeof(CONFIG_AWS_IOT_BROKER_HOST_NAME) - 1,
			 "AWS IoT host name static buffer too small "
			 "Increase CONFIG_AWS_IOT_BROKER_HOST_NAME_MAX_LEN");
#endif 
#if defined(CONFIG_AWS_IOT_IPV6)
#define AWS_AF_FAMILY AF_INET6
#else
#define AWS_AF_FAMILY AF_INET
#endif
static char *client_id_buf = CONFIG_AWS_THING_NAME;
#define AWS_IOT_SHADOW_REQUEST_STRING ""
static char dms_host_name_buf[] ="a2sdhgohxx19hy-ats.iot.ap-south-1.amazonaws.com";
static char cattle_host_name_buf[] ="vanix-mybovin-iot-core.voltrackvanix.com";
#define MQTT_PORT 8883
static char aws_host_name_buf[CONFIG_AWS_IOT_BROKER_HOST_NAME_MAX_LEN + 1];
static struct aws_iot_app_topic_data app_topic_data;
static struct mqtt_client client;
static struct sockaddr_storage broker;
static struct mqtt_client client_dms;

static struct sockaddr_storage broker_dms;

static uint8_t rx_buffer_dms[1024];
static uint8_t tx_buffer_dms[1024];


static char client_id_buf_dms[]    = "dms-client-id";
static bool sntp_synced = false;

static char rx_buffer[CONFIG_AWS_IOT_MQTT_RX_TX_BUFFER_LEN];
static char tx_buffer[CONFIG_AWS_IOT_MQTT_RX_TX_BUFFER_LEN];
static char payload_buf[CONFIG_AWS_IOT_MQTT_PAYLOAD_BUFFER_LEN];
static aws_iot_evt_handler_t module_evt_handler;
static atomic_t disconnect_requested;
static atomic_t connection_poll_active;
static atomic_t aws_iot_disconnected = ATOMIC_INIT(1);
static const struct json_obj_descr device_info_descr[] = {JSON_OBJ_DESCR_PRIM(struct device_info, device_imei, JSON_TOK_STRING), JSON_OBJ_DESCR_PRIM(struct device_info, device_restart, JSON_TOK_TRUE),
};
static const struct json_obj_descr restart_payload_descr[] = {  JSON_OBJ_DESCR_OBJECT(struct restart_payload, device, device_info_descr),
};

#define SEC_TAG_S3 12


#define DFU_STREAM_BUF_SIZE 4096
static uint8_t dfu_stream_buf[DFU_STREAM_BUF_SIZE] __aligned(4);
void handle_device_restart_request(const uint8_t *payload, size_t len) 
{
    struct restart_payload data = {0};
    int ret = json_obj_parse((char *)payload, len, restart_payload_descr,  ARRAY_SIZE(restart_payload_descr), &data);
    if (ret < 0) {
        LOG_ERR("DMS: Failed to parse restart JSON (%d)", ret);
        return;
    }
    if (data.device.device_imei != NULL && strcmp(data.device.device_imei, MY_DEVICE_IMEI) == 0) {
        if (data.device.device_restart) {
            LOG_WRN("DMS: Valid Restart Command Received for IMEI: %s", data.device.device_imei);
            LOG_INF("System rebooting in 2 seconds...");
            k_sleep(K_MSEC(2000)); // Allow logs to flush and MQTT ACKs to send
            sys_reboot(SYS_REBOOT_COLD);
        }
    } else {
        LOG_WRN("DMS: Restart ignored. IMEI mismatch (Got: %s)",  data.device.device_imei ? data.device.device_imei : "NULL");
    }
}
static K_SEM_DEFINE(connection_poll_sem, 0, 1);
static int connect_error_translate(const int err)
{
	switch (err)
	{
	case 0:
		return AWS_IOT_CONNECT_RES_SUCCESS;
	case -ECHILD:
		return AWS_IOT_CONNECT_RES_ERR_NETWORK;
	case -EACCES:
		return AWS_IOT_CONNECT_RES_ERR_NOT_INITD;
	case -ENOEXEC:
		return AWS_IOT_CONNECT_RES_ERR_BACKEND;
	case -EINVAL:
		return AWS_IOT_CONNECT_RES_ERR_PRV_KEY;
	case -EOPNOTSUPP:
		return AWS_IOT_CONNECT_RES_ERR_CERT;
	case -ECONNREFUSED:
		return AWS_IOT_CONNECT_RES_ERR_CERT_MISC;
	case -ETIMEDOUT:
		return AWS_IOT_CONNECT_RES_ERR_TIMEOUT_NO_DATA;
	case -ENOMEM:
		return AWS_IOT_CONNECT_RES_ERR_NO_MEM;
	case -EINPROGRESS:
		return AWS_IOT_CONNECT_RES_ERR_ALREADY_CONNECTED;
	default:
		LOG_ERR("AWS broker connect failed %d", err);
		return AWS_IOT_CONNECT_RES_ERR_MISC;
	}
}
static void aws_iot_notify_event(const struct aws_iot_evt *aws_iot_evt)
{
	if ((module_evt_handler != NULL) && (aws_iot_evt != NULL))
	{
		module_evt_handler(aws_iot_evt);
	}
	else
	{
		LOG_ERR("Library event handler not registered, or empty event");
	}
}

#define DMS_SERVER_IP   "65.1.96.216"
#define DMS_SERVER_PORT 3600
#define AUTH_TOKEN      "a12b4e1bed6a2aba55081ff587e670c15a795526669f8abf416d2eb785562c82"

static char fota_presigned_url[2560] = {0};
static char fota_latest_version[32] = {0};

/* Shared FOTA buffers at file scope to avoid RAM overflow */
static char    fota_response[6144];
static char    fota_host_buf[256];
static char    fota_path_buf[2560];



static char    fota_http_request[4096] = {0};
static uint8_t fota_recv_buf[4096];
static char    fota_header_buf[1024];
#define FOTA_PREFETCH_SIZE  4096

 void fota_dl_handler(const struct fota_download_evt *evt)
{
    switch (evt->id) {

    case FOTA_DOWNLOAD_EVT_PROGRESS:
        LOG_INF("FOTA progress: %d%%", evt->progress);
        break;

    case FOTA_DOWNLOAD_EVT_FINISHED:
        LOG_INF("FOTA download finished");

        fota_report_status(MY_DEVICE_IMEI, fota_latest_version, "Success");

        k_sleep(K_SECONDS(2));
        sys_reboot(SYS_REBOOT_COLD);
        break;

    case FOTA_DOWNLOAD_EVT_ERROR:
        LOG_ERR("FOTA download error");

        fota_report_status(MY_DEVICE_IMEI, fota_latest_version, "Failed");

        atomic_set(&fota_active, 0);
        break;

    default:
        break;
    }
}

int fota_report_status(const char *device_imei, const char *version,
                       const char *status)
{
    int sock, err;
    struct sockaddr_in server;
    char body[256];
    char request[640];
    char response[512];

    snprintf(body, sizeof(body),
             "{\"message\":3,\"device_id\":\"%s\","
             "\"installed_version\":\"%s\","
             "\"status\":\"%s\","
             "\"timestamp\":%lld}",
             device_imei, version, status,
             (long long)(k_uptime_get() / 1000));

    snprintf(request, sizeof(request),
             "POST /v1/api/device/firmware/update-upload-status HTTP/1.1\r\n"
             "Host: %s:%d\r\n"
             "Authorization: %s\r\n"
             "Content-Type: application/json\r\n"
             "Content-Length: %d\r\n"
             "Connection: close\r\n\r\n%s",
             DMS_SERVER_IP, DMS_SERVER_PORT, AUTH_TOKEN,
             strlen(body), body);

    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock < 0) { LOG_ERR("fota_report socket failed"); return -errno; }

    server.sin_family = AF_INET;
    server.sin_port = htons(DMS_SERVER_PORT);
    net_addr_pton(AF_INET, DMS_SERVER_IP, &server.sin_addr);

    err = connect(sock, (struct sockaddr *)&server, sizeof(server));
    if (err < 0) { close(sock); return -errno; }

    send(sock, request, strlen(request), 0);
    recv(sock, response, sizeof(response) - 1, 0);
    LOG_INF("FOTA status report: %s", status);
    close(sock);
    return 0;
}
int fota_check_and_start(const char *device_imei)
{
    int sock, err;
    struct sockaddr_in server;
    char url_path[128];
    char request[512];
    int total = 0, received;
    static int fota_attempt_count = 0;
    
    printk(">>> FOTA: entering fota_check_and_start\n"); 

    atomic_set(&fota_active, 1);
    k_sleep(K_MSEC(300));
    printk(">>> FOTA: after sleep, attempt=%d\n", fota_attempt_count);

    if (fota_attempt_count >= 3) {
        LOG_ERR("FOTA failed 3 times, giving up");
        atomic_set(&fota_active, 0);
        fota_attempt_count = 0;
        return 1;
    }

    /* ── STEP 1: Fetch firmware metadata from DMS ── */
    snprintf(url_path, sizeof(url_path),
             "/v1/api/device/firmware/get-firmware-by-device-id/%s",
             device_imei);

    snprintf(request, sizeof(request),
         "GET %s HTTP/1.1\r\n"
         "Host: %s:%d\r\n"
         "Authorization: %s\r\n"
         "User-Agent: BMSDevice\r\n"
         "Connection: close\r\n\r\n",
         url_path,
         DMS_SERVER_IP,
         DMS_SERVER_PORT,
         AUTH_TOKEN);

    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock < 0) { LOG_ERR("fota_check socket failed"); return -errno; }
    
    struct timeval tv = { .tv_sec = 15, .tv_usec = 0 };
    setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
    setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    
    server.sin_family = AF_INET;
    server.sin_port   = htons(DMS_SERVER_PORT);
    net_addr_pton(AF_INET, DMS_SERVER_IP, &server.sin_addr);

    err = connect(sock, (struct sockaddr *)&server, sizeof(server));
    if (err < 0) { close(sock); return -errno; }

    send(sock, request, strlen(request), 0);

    memset(fota_response, 0, sizeof(fota_response));
    total = 0;
    struct pollfd pfd = { .fd = sock, .events = POLLIN };
    while(1){
        int p = poll(&pfd, 1, 10000); 
        if (p <= 0) break;
        received = recv(sock, fota_response + total,
                        sizeof(fota_response) - total - 1, 0);
        if (received <= 0) break;
        total += received;
    }
    close(sock);

    char *json_body = strstr(fota_response, "\r\n\r\n");
    if (!json_body) {
        atomic_set(&fota_active, 0);
        return -EINVAL;
    }

    if (!strstr(fota_response, "\"is_update_available\":true") &&
        !strstr(fota_response, "\"is_update_available\": true")) {
        LOG_INF("No firmware update available");
        atomic_set(&fota_active, 0);
        return 1;
    }

    /* Extract version */
    char *ver_start = strstr(fota_response, "\"latest_version\":\"");
    if (ver_start) {
        ver_start += strlen("\"latest_version\":\"");
        char *ver_end = strchr(ver_start, '"');
        if (ver_end) {
            memset(fota_latest_version, 0, sizeof(fota_latest_version));
            strncpy(fota_latest_version, ver_start,
                    MIN((size_t)(ver_end - ver_start),
                        sizeof(fota_latest_version) - 1));
        }
    }

    /* Extract presigned URL */
    char *url_start_ptr = strstr(fota_response, "\"presigned_url\":\"");
    if (!url_start_ptr) {
        url_start_ptr = strstr(fota_response, "\"presigned_url\" : \"");
        if (!url_start_ptr) {
            atomic_set(&fota_active, 0);
            return -EINVAL;
        }
        url_start_ptr += strlen("\"presigned_url\" : \"");
    } else {
        url_start_ptr += strlen("\"presigned_url\":\"");
    }

    char *url_end_ptr = url_start_ptr;
    while (*url_end_ptr) {
        if (*url_end_ptr == '"' && *(url_end_ptr - 1) != '\\') break;
        url_end_ptr++;
    }
    
    size_t url_len = url_end_ptr - url_start_ptr;

    /* Safe Unescaping (Crucial for S3) */
    memset(fota_presigned_url, 0, sizeof(fota_presigned_url));
    int dst_idx = 0;
    for (size_t i = 0; i < url_len && dst_idx < sizeof(fota_presigned_url) - 1; i++) {
        if (url_start_ptr[i] == '\\' && (url_start_ptr[i+1] == '/' || url_start_ptr[i+1] == '"')) {
            fota_presigned_url[dst_idx++] = url_start_ptr[i+1];
            i++; 
        } else {
            fota_presigned_url[dst_idx++] = url_start_ptr[i];
        }
    }
    fota_presigned_url[dst_idx] = '\0';

    /* Diagnostic Print Loop */
    {
        int _plen = strlen(fota_presigned_url);
        printk("\n=== FULL URL (%d chars) ===\n", _plen);
        for (int _i = 0; _i < _plen; _i += 60) {
            printk("%.60s", fota_presigned_url + _i);
            k_busy_wait(2000); 
        }
        printk("\n=== END URL ===\n");
    }

    /* --- Robust S3 Host/Path Split --- */
    char *const fota_host = fota_host_buf;
    char *const fota_path = fota_path_buf;

    char *host_ptr = strstr(fota_presigned_url, "://");
    if (!host_ptr) return -EINVAL;
    host_ptr += 3; 

    char *path_ptr = strchr(host_ptr, '/');
    if (!path_ptr) return -EINVAL;

    size_t host_len = path_ptr - host_ptr;
    if (host_len >= 256) host_len = 255;
    memset(fota_host, 0, 256);
    memcpy(fota_host, host_ptr, host_len);
    fota_host[host_len] = '\0'; 

    memset(fota_path, 0, sizeof(fota_path_buf));
    strncpy(fota_path, path_ptr, sizeof(fota_path_buf) - 1);
    
    LOG_INF("S3 Host detected: %s", fota_host);
    LOG_INF("S3 Path Length: %d", (int)strlen(fota_path));

    /* ── STEP 2: Disconnect MQTT ── */
    LOG_INF("Disconnecting MQTT...");
    mqtt_disconnect(&client_dms);
    mqtt_disconnect(&client);
    atomic_set(&aws_iot_disconnected, 1);
    k_sleep(K_SECONDS(3));

    /* ── STEP 3: Provision S3 cert ── */
    tls_credential_delete(SEC_TAG_S3, TLS_CREDENTIAL_CA_CERTIFICATE);
    tls_credential_add(SEC_TAG_S3, TLS_CREDENTIAL_CA_CERTIFICATE,
                       dms_ca_certificate, sizeof(dms_ca_certificate));

    /* ── STEP 4: Start Download with NULL Headers ── */
    // Using NULL, 0 prevents the library from adding headers that invalidate S3 signatures
    err = fota_download_start(fota_host, fota_path, SEC_TAG_S3, NULL, 0);

    if (err) {
        LOG_ERR("FOTA start failed: %d", err);
        atomic_set(&fota_active, 0);
        fota_report_status(device_imei, fota_latest_version, "Failed");
        fota_attempt_count++;
        return err;
    }

    LOG_INF("FOTA download started...");
    return 0; 
}
    
      























#if defined(CONFIG_AWS_FOTA)
static void aws_fota_cb_handler(struct aws_fota_event *fota_evt)
{
	struct aws_iot_evt aws_iot_evt = {0};

	if (fota_evt == NULL)
	{
		return;
	}
	switch (fota_evt->id)
	{
	case AWS_FOTA_EVT_START:
		LOG_DBG("AWS_FOTA_EVT_START");
		aws_iot_evt.type = AWS_IOT_EVT_FOTA_START;
		break;
	case AWS_FOTA_EVT_DONE:
		LOG_DBG("AWS_FOTA_EVT_DONE");
fota_report_status(MY_DEVICE_IMEI, fota_latest_version, "Success");
    aws_iot_evt.type = AWS_IOT_EVT_FOTA_DONE;
    aws_iot_evt.data.image = fota_evt->image;

		break;
	case AWS_FOTA_EVT_ERASE_PENDING:
		LOG_DBG("AWS_FOTA_EVT_ERASE_PENDING");
		aws_iot_evt.type = AWS_IOT_EVT_FOTA_ERASE_PENDING;
		break;
	case AWS_FOTA_EVT_ERASE_DONE:
		LOG_DBG("AWS_FOTA_EVT_ERASE_DONE");
		aws_iot_evt.type = AWS_IOT_EVT_FOTA_ERASE_DONE;
		break;
	case AWS_FOTA_EVT_ERROR:

        LOG_ERR("AWS_FOTA_EVT_ERROR");
    fota_report_status(MY_DEVICE_IMEI, fota_latest_version, "Failed");
    aws_iot_evt.type = AWS_IOT_EVT_FOTA_ERROR;
		break;
	case AWS_FOTA_EVT_DL_PROGRESS:
		LOG_DBG("AWS_FOTA_EVT_DL_PROGRESS, (%d%%)",
				fota_evt->dl.progress);
		aws_iot_evt.type = AWS_IOT_EVT_FOTA_DL_PROGRESS;
		aws_iot_evt.data.fota_progress = fota_evt->dl.progress;
		break;
	default:
		LOG_ERR("Unknown FOTA event");
		return;
	}
	aws_iot_notify_event(&aws_iot_evt);
}
#endif
static int aws_iot_set_host_name(char *const host_name, size_t host_name_len)
{
	if (host_name == NULL)
	{
		return -EINVAL;
	}
	if (host_name_len >= sizeof(aws_host_name_buf))
	{
		return -ENOMEM;
	}
	memcpy(aws_host_name_buf, host_name, host_name_len);
	aws_host_name_buf[host_name_len] = '\0';
	return 0;
}
static int publish_get_payload(struct mqtt_client *const c, size_t length)
{
	if (length > sizeof(payload_buf))
	{
		LOG_ERR("Incoming MQTT message too large for payload buffer");
		return -EMSGSIZE;
	}
	return mqtt_readall_publish_payload(c, payload_buf, length);
}
static K_SEM_DEFINE(cloud_ready_sem, 0, 1);

void aws_iot_wait_for_connection(void)
{
    k_sem_take(&cloud_ready_sem, K_FOREVER);
}
static void mqtt_evt_handler(struct mqtt_client *const c, const struct mqtt_evt *mqtt_evt)
{
    int err;
    struct aws_iot_evt aws_iot_evt = {0};

    /* --- STEP 1: DMS SPECIFIC LOGIC (SEC_TAG 10) --- */
    if (c == &client_dms) {
        switch (mqtt_evt->type) {
        case MQTT_EVT_CONNACK:
            if (mqtt_evt->param.connack.return_code == 0) {
                LOG_INF("DMS MQTT client connected!");
                
                /* Dynamically create the restart topic using the device IMEI */
                static char dms_topic[64];
                snprintf(dms_topic, sizeof(dms_topic), "vanix/restart-device/%s", MY_DEVICE_IMEI);

                struct mqtt_topic dms_sub_topic = {
                    .topic.utf8 = dms_topic,
                    .topic.size = strlen(dms_topic),
                    .qos = MQTT_QOS_1_AT_LEAST_ONCE
                };
                struct mqtt_subscription_list sub_list = {
                    .list = &dms_sub_topic,
                    .list_count = 1,
                    .message_id = k_cycle_get_32()
                };
                err = mqtt_subscribe(c, &sub_list);
                if (err) {
                    LOG_ERR("DMS Subscription failed: %d", err);
                } else {
                    LOG_INF("DMS subscribed to: %s", dms_topic);
                }
            }
            break;

        case MQTT_EVT_PUBLISH: {
            const struct mqtt_publish_param *p = &mqtt_evt->param.publish;
            
            LOG_INF("DMS Message received on topic: %.*s", 
                    p->message.topic.topic.size, p->message.topic.topic.utf8);

            err = publish_get_payload(c, p->message.payload.len);
            if (err == 0) {
                /* Execute the restart logic */
                handle_device_restart_request(payload_buf, p->message.payload.len);
            }

            if (p->message.topic.qos == MQTT_QOS_1_AT_LEAST_ONCE) {
                const struct mqtt_puback_param ack = { .message_id = p->message_id };
                mqtt_publish_qos1_ack(c, &ack);
            }
            break;
        }

        case MQTT_EVT_DISCONNECT:
            LOG_WRN("DMS MQTT client disconnected: %d", mqtt_evt->result);
            break;

        default:
            break;
        }
        /* Exit here so DMS events don't leak into Cattle logic below */
        return; 
    }

    /* --- STEP 2: CATTLE / AWS IOT LIBRARY LOGIC (SEC_TAG 11) --- */



    switch (mqtt_evt->type) {
    case MQTT_EVT_CONNACK:
        if (mqtt_evt->param.connack.return_code) {
            LOG_ERR("Cattle MQTT_EVT_CONNACK, error: %d", mqtt_evt->param.connack.return_code);
            aws_iot_evt.data.err = mqtt_evt->param.connack.return_code;
            aws_iot_evt.type = AWS_IOT_EVT_ERROR;
            aws_iot_notify_event(&aws_iot_evt);
            break;
        }

        LOG_DBG("Cattle MQTT client connected!");

        if (app_topic_data.list_count > 0) {
            struct mqtt_subscription_list sub_list = {
                .list       = app_topic_data.list,
                .list_count = app_topic_data.list_count,
                .message_id = k_cycle_get_32(),
            };

            err = mqtt_subscribe(c, &sub_list);
            if (err) {
                LOG_ERR("Cattle MQTT Subscribe failed: %d", err);
            } else {
                LOG_INF("Cattle subscribing to %d topics...", app_topic_data.list_count);
            }
        }

        aws_iot_evt.data.persistent_session =
            !IS_ENABLED(CONFIG_MQTT_CLEAN_SESSION) &&
            mqtt_evt->param.connack.session_present_flag;
        aws_iot_evt.type = AWS_IOT_EVT_CONNECTED;
        aws_iot_notify_event(&aws_iot_evt);

        aws_iot_evt.type = AWS_IOT_EVT_READY;
        aws_iot_notify_event(&aws_iot_evt);
         k_sem_give(&cloud_ready_sem); 
        break;

    case MQTT_EVT_DISCONNECT:
        LOG_DBG("Cattle MQTT_EVT_DISCONNECT: result = %d", mqtt_evt->result);
        aws_iot_evt.data.err = AWS_IOT_DISCONNECT_MISC;

        if (atomic_get(&disconnect_requested)) {
            aws_iot_evt.data.err = AWS_IOT_DISCONNECT_USER_REQUEST;
        }

        atomic_set(&aws_iot_disconnected, 1);
        aws_iot_evt.type = AWS_IOT_EVT_DISCONNECTED;
        aws_iot_notify_event(&aws_iot_evt);
        break;

    case MQTT_EVT_PUBLISH: {
        const struct mqtt_publish_param *p = &mqtt_evt->param.publish;

        LOG_DBG("Cattle MQTT_EVT_PUBLISH: id = %d len = %d ",
                p->message_id, p->message.payload.len);

        err = publish_get_payload(c, p->message.payload.len);
        if (err) {
            LOG_ERR("Cattle publish_get_payload, error: %d", err);
            break;
        }

        if (p->message.topic.qos == MQTT_QOS_1_AT_LEAST_ONCE) {
            const struct mqtt_puback_param ack = { .message_id = p->message_id };
            mqtt_publish_qos1_ack(c, &ack);
        }

        aws_iot_evt.type = AWS_IOT_EVT_DATA_RECEIVED;
        aws_iot_evt.data.msg.ptr        = payload_buf;
        aws_iot_evt.data.msg.len        = p->message.payload.len;
        aws_iot_evt.data.msg.topic.str  = p->message.topic.topic.utf8;
        aws_iot_evt.data.msg.topic.len  = p->message.topic.topic.size;

        aws_iot_notify_event(&aws_iot_evt);
        break;
    }

    case MQTT_EVT_PUBACK:
        LOG_DBG("Cattle MQTT_EVT_PUBACK: id = %d", mqtt_evt->param.puback.message_id);
        aws_iot_evt.type = AWS_IOT_EVT_PUBACK;
        aws_iot_evt.data.message_id = mqtt_evt->param.puback.message_id;
        aws_iot_notify_event(&aws_iot_evt);
        break;

    case MQTT_EVT_PINGRESP:
        LOG_DBG("Cattle MQTT_EVT_PINGRESP");
        aws_iot_evt.type = AWS_IOT_EVT_PINGRESP;
        aws_iot_notify_event(&aws_iot_evt);
        break;

    default:
        break;
    }
}

    /* Add DMS Private Key */
  
static int broker_init_dms(void)
{
    int err;
    struct addrinfo *result;
    struct addrinfo *addr;
    struct addrinfo hints = {
        .ai_family   = AF_INET,
        .ai_socktype = SOCK_STREAM,
    };
    err = getaddrinfo(dms_host_name_buf, NULL, &hints, &result);
    if (err) {
        LOG_ERR("getaddrinfo DMS, error %d", err);
        return -ECHILD;
    }
    addr = result;
    while (addr != NULL) {
        if (addr->ai_addrlen == sizeof(struct sockaddr_in)) {
            struct sockaddr_in *broker4 =
                (struct sockaddr_in *)&broker_dms;
            broker4->sin_addr.s_addr =
                ((struct sockaddr_in *)addr->ai_addr)->sin_addr.s_addr;
            broker4->sin_family = AF_INET;
            broker4->sin_port   = htons(MQTT_PORT);
            break;
        }
        addr = addr->ai_next;
    }
    freeaddrinfo(result);
    return 0;
}
// static int broker_init_cattle(void)
// {
//     int err;
//     struct addrinfo *result;
//     struct addrinfo *addr;
//     struct addrinfo hints = {
//         .ai_family   = AF_INET,
//         .ai_socktype = SOCK_STREAM,
//     };
//     err = getaddrinfo(cattle_host_name_buf, NULL, &hints, &result);
//     if (err) {
//         LOG_ERR("getaddrinfo cattle, error %d", err);
//         return -ECHILD;
//     }
//     addr = result;
//     while (addr != NULL) {
//         if (addr->ai_addrlen == sizeof(struct sockaddr_in)) {
//             struct sockaddr_in *broker4 =
//                 (struct sockaddr_in *)&broker_cattle;
//             broker4->sin_addr.s_addr =
//                 ((struct sockaddr_in *)addr->ai_addr)->sin_addr.s_addr;
//             broker4->sin_family = AF_INET;
//             broker4->sin_port   = htons(MQTT_PORT);
//             break;
//         }
//         addr = addr->ai_next;
//     }
//     freeaddrinfo(result);
//     return 0;
// }
/* --- SECTION: PROVISIONING (Fixes Undefined Reference) --- */

static int certificates_provision_dms(void)
{
    static bool dms_certs_added;
    if (dms_certs_added) return 0;

    tls_credential_delete(SEC_TAG_DMS, TLS_CREDENTIAL_CA_CERTIFICATE);
    tls_credential_delete(SEC_TAG_DMS, TLS_CREDENTIAL_PRIVATE_KEY);
    tls_credential_delete(SEC_TAG_DMS, TLS_CREDENTIAL_SERVER_CERTIFICATE);

    int err = tls_credential_add(SEC_TAG_DMS, TLS_CREDENTIAL_CA_CERTIFICATE, dms_ca_certificate, sizeof(dms_ca_certificate));
    if (err < 0) return err;
    err = tls_credential_add(SEC_TAG_DMS, TLS_CREDENTIAL_PRIVATE_KEY, dms_private_key, sizeof(dms_private_key));
    if (err < 0) return err;
    err = tls_credential_add(SEC_TAG_DMS, TLS_CREDENTIAL_SERVER_CERTIFICATE, dms_device_certificate, sizeof(dms_device_certificate));
    if (err < 0) return err;

    dms_certs_added = true;
    LOG_INF("DMS Certs OK");
    return 0;
}

static int certificates_provision_cattle(void)
{
    static bool cattle_certs_added;
    if (cattle_certs_added) return 0;

    tls_credential_delete(SEC_TAG_CATTLE, TLS_CREDENTIAL_CA_CERTIFICATE);
    tls_credential_delete(SEC_TAG_CATTLE, TLS_CREDENTIAL_PRIVATE_KEY);
    tls_credential_delete(SEC_TAG_CATTLE, TLS_CREDENTIAL_SERVER_CERTIFICATE);

    int err = tls_credential_add(SEC_TAG_CATTLE, TLS_CREDENTIAL_CA_CERTIFICATE, cattle_ca_certificate, sizeof(cattle_ca_certificate));
    if (err < 0) return err;
    err = tls_credential_add(SEC_TAG_CATTLE, TLS_CREDENTIAL_PRIVATE_KEY, cattle_private_key, sizeof(cattle_private_key));
    if (err < 0) return err;
    err = tls_credential_add(SEC_TAG_CATTLE, TLS_CREDENTIAL_SERVER_CERTIFICATE, cattle_device_certificate, sizeof(cattle_device_certificate));
    if (err < 0) return err;

    cattle_certs_added = true;
    LOG_INF("Cattle Certs OK");
    return 0;
}

/* --- SECTION: INITIALIZATION (Calls the functions above) --- */

static int client_broker_init_dms(struct mqtt_client *const client)
{
    mqtt_client_init(client);
    client->keepalive = 120;
    if (broker_init_dms() != 0) return -ECHILD;

    client->broker           = &broker_dms;
    client->evt_cb           = mqtt_evt_handler;
    client->client_id.utf8   = client_id_buf_dms;
    client->client_id.size   = strlen(client_id_buf_dms);
    client->rx_buf           = rx_buffer_dms;
    client->rx_buf_size      = sizeof(rx_buffer_dms);
    client->tx_buf           = tx_buffer_dms;
    client->tx_buf_size      = sizeof(tx_buffer_dms);
    client->transport.type   = MQTT_TRANSPORT_SECURE;

    static sec_tag_t tags_dms[] = { SEC_TAG_DMS };
    client->transport.tls.config.sec_tag_list = tags_dms;
    client->transport.tls.config.sec_tag_count = 1;
    client->transport.tls.config.peer_verify = 2;
    client->transport.tls.config.hostname = dms_host_name_buf;

    return certificates_provision_dms();
}

// static int client_broker_init_cattle(struct mqtt_client *const client)
// {
//     mqtt_client_init(client);
//     client->keepalive = 120;
//     if (broker_init_cattle() != 0) return -ECHILD;

//     client->broker           = &broker_cattle;
//     client->evt_cb           = mqtt_evt_handler;
//     client->client_id.utf8   = client_id_buf_cattle;
//     client->client_id.size   = strlen(client_id_buf_cattle);
//     client->rx_buf           = rx_buffer_cattle;
//     client->rx_buf_size      = sizeof(rx_buffer_cattle);
//     client->tx_buf           = tx_buffer_cattle;
//     client->tx_buf_size      = sizeof(tx_buffer_cattle);
//     client->transport.type   = MQTT_TRANSPORT_SECURE;

//     static sec_tag_t tags_cattle[] = { SEC_TAG_CATTLE };
//     client->transport.tls.config.sec_tag_list = tags_cattle;
//     client->transport.tls.config.sec_tag_count = 1;
//     client->transport.tls.config.peer_verify = 2;
//     client->transport.tls.config.hostname = cattle_host_name_buf;

//     return certificates_provision_cattle();
// }
	/* --- SECTION: CONNECTION WRAPPERS --- */

int connect_client_dms(void)
{
    int err;
    err = client_broker_init_dms(&client_dms);
    if (err) {
        LOG_ERR("client_broker_init_dms, error: %d", err);
        return err;
    }
    err = mqtt_connect(&client_dms);
    if (err) {
        LOG_ERR("mqtt_connect DMS, error: %d", err);
        return err;
    }
    return 0;
}

// int connect_client_cattle(void)
// {
//     int err;
//     err = client_broker_init_cattle(&client_cattle);
//     if (err) {
//         LOG_ERR("client_broker_init_cattle, error: %d", err);
//         return err;
//     }
//     err = mqtt_connect(&client_cattle);
//     if (err) {
//         LOG_ERR("mqtt_connect cattle, error: %d", err);
//         return err;
//     }
//     return 0;
// }
#if defined(CONFIG_AWS_IOT_STATIC_IPV4)
static int broker_init(void)
{
	struct sockaddr_in *broker4 =((struct sockaddr_in *)&broker);
	inet_pton(AF_INET, CONFIG_AWS_IOT_STATIC_IPV4_ADDR, &broker4->sin_addr);
	broker4->sin_family = AF_INET;
	broker4->sin_port = htons(CONFIG_AWS_IOT_PORT);
	LOG_DBG("IPv4 Address %s", CONFIG_AWS_IOT_STATIC_IPV4_ADDR);
	return 0;
}
#else
static int broker_init(void)
{
	int err;
	struct addrinfo *result;
	struct addrinfo *addr;
	struct addrinfo hints = {.ai_family = AWS_AF_FAMILY,.ai_socktype = SOCK_STREAM};
	err = getaddrinfo(aws_host_name_buf, NULL, &hints, &result);
	if (err)
	{
		LOG_ERR("getaddrinfo, error %d", err);
		return -ECHILD;
	}
	addr = result;
	while (addr != NULL)
	{
		if ((addr->ai_addrlen == sizeof(struct sockaddr_in)) &&
			(AWS_AF_FAMILY == AF_INET))
		{
			struct sockaddr_in *broker4 =((struct sockaddr_in *)&broker);
			char ipv4_addr[NET_IPV4_ADDR_LEN];
			broker4->sin_addr.s_addr =
				((struct sockaddr_in *)addr->ai_addr)->sin_addr.s_addr;
			broker4->sin_family = AF_INET;
			broker4->sin_port = htons(CONFIG_AWS_IOT_PORT);
			inet_ntop(AF_INET, &broker4->sin_addr.s_addr, ipv4_addr,
					  sizeof(ipv4_addr));
			LOG_DBG("IPv4 Address found %s", ipv4_addr);
			break;
		}
		else if ((addr->ai_addrlen == sizeof(struct sockaddr_in6)) &&
				 (AWS_AF_FAMILY == AF_INET6))
		{
			struct sockaddr_in6 *broker6 =
				((struct sockaddr_in6 *)&broker);
			char ipv6_addr[NET_IPV6_ADDR_LEN];
			memcpy(broker6->sin6_addr.s6_addr,
				   ((struct sockaddr_in6 *)addr->ai_addr)
					   ->sin6_addr.s6_addr,
				   sizeof(struct in6_addr));
			broker6->sin6_family = AF_INET6;
			broker6->sin6_port = htons(CONFIG_AWS_IOT_PORT);
			inet_ntop(AF_INET6, &broker6->sin6_addr.s6_addr,
					  ipv6_addr, sizeof(ipv6_addr));
			LOG_DBG("IPv4 Address found %s", ipv6_addr);
			break;
		}
		LOG_DBG("ai_addrlen = %u should be %u or %u",
				(unsigned int)addr->ai_addrlen,
				(unsigned int)sizeof(struct sockaddr_in),
				(unsigned int)sizeof(struct sockaddr_in6));
		addr = addr->ai_next;
		break;
	}
	freeaddrinfo(result);
	return err;
}
#endif 
static int client_broker_init(struct mqtt_client *const client)
{
	int err;
	mqtt_client_init(client);
	err = broker_init();
	if (err)
	{
		return err;
	}
	client->broker = &broker;
	client->evt_cb = mqtt_evt_handler;
	client->client_id.utf8 = client_id_buf;
	client->client_id.size = strlen(client_id_buf);
	client->password = NULL;
	client->user_name = NULL;
	client->protocol_version = MQTT_VERSION_3_1_1;
	client->rx_buf = rx_buffer;
	client->rx_buf_size = sizeof(rx_buffer);
	client->tx_buf = tx_buffer;
	client->tx_buf_size = sizeof(tx_buffer);
	client->transport.type = MQTT_TRANSPORT_SECURE;
#if defined(CONFIG_AWS_IOT_LAST_WILL)
	static struct mqtt_topic last_will_topic = {.topic.utf8 = CONFIG_AWS_IOT_LAST_WILL_TOPIC,
.topic.size = sizeof(CONFIG_AWS_IOT_LAST_WILL_TOPIC) - 1,
		.qos = MQTT_QOS_0_AT_MOST_ONCE};
	static struct mqtt_utf8 last_will_message = {
		.utf8 = CONFIG_AWS_IOT_LAST_WILL_MESSAGE,
		.size = sizeof(CONFIG_AWS_IOT_LAST_WILL_MESSAGE) - 1};
	client->will_topic = &last_will_topic;
	client->will_message = &last_will_message;
#endif
	static sec_tag_t sec_tag_list[] = {CONFIG_AWS_IOT_SEC_TAG};
	struct mqtt_sec_config *tls_cfg = &(client->transport).tls.config;
	tls_cfg->peer_verify = 2;
	tls_cfg->cipher_count = 0;
	tls_cfg->cipher_list = NULL;
	tls_cfg->sec_tag_count = ARRAY_SIZE(sec_tag_list);
	tls_cfg->sec_tag_list = sec_tag_list;
	tls_cfg->hostname = aws_host_name_buf;
	tls_cfg->session_cache = TLS_SESSION_CACHE_DISABLED;
#if defined(CONFIG_AWS_IOT_PROVISION_CERTIFICATES)
	err = certificates_provision_cattle();
	if (err)
	{
		LOG_ERR("Could not provision certificates, error: %d", err);
		return err;
	}
#endif 
	return err;
}
static int connect_client(struct aws_iot_config *const config)
{
	int err;
	err = client_broker_init(&client);
	if (err)
	{
		LOG_ERR("client_broker_init, error: %d", err);
		return err;
	}
	err = mqtt_connect(&client);
	if (err)
	{
		LOG_ERR("mqtt_connect, error: %d", err);
		err = connect_error_translate(err);
		return err;
	}
#if defined(CONFIG_AWS_IOT_SEND_TIMEOUT)
	struct timeval timeout = {
		.tv_sec = CONFIG_AWS_IOT_SEND_TIMEOUT_SEC};

	err = setsockopt(client.transport.tls.sock,
					 SOL_SOCKET,
					 SO_SNDTIMEO,
					 &timeout,
					 sizeof(timeout));
	if (err == -1)
	{
		LOG_WRN("Failed to set timeout, errno: %d", errno);
		err = 0;
	}
	else
	{
		LOG_DBG("Using send socket timeout of %d seconds",CONFIG_AWS_IOT_SEND_TIMEOUT_SEC);
	}
#endif 
	if (config != NULL)
	{
		config->socket = client.transport.tls.sock;
	}
	return 0;
}
static int connection_poll_start(void)
{
	if (atomic_get(&connection_poll_active))
	{
		LOG_DBG("Connection poll in progress");
		return -EINPROGRESS;
	}
	atomic_set(&disconnect_requested, 0);
	k_sem_give(&connection_poll_sem);
	return 0;
}
int aws_iot_ping(void)
{
	if (client.unacked_ping)
	{
		LOG_DBG("Previous MQTT ping not acknowledged");
		return -ECONNRESET;
	}
	return mqtt_ping(&client);
}
int aws_iot_keepalive_time_left(void)
{
	return mqtt_keepalive_time_left(&client);
}
int aws_iot_input(void)
{
	return mqtt_input(&client);
}
int aws_iot_send(const struct aws_iot_data *const tx_data)
{
    if (atomic_get(&fota_active)) {
        LOG_WRN("FOTA in progress, dropping publish request.");
        return -EBUSY; 
    }
	struct aws_iot_data tx_data_pub = {
		.ptr = tx_data->ptr,
		.len = tx_data->len,
		.qos = tx_data->qos,
		.message_id = tx_data->message_id,
		.retain_flag = tx_data->retain_flag,
		.dup_flag = tx_data->dup_flag,
		.topic.type = tx_data->topic.type,
		.topic.str = tx_data->topic.str,
		.topic.len = tx_data->topic.len};
	struct mqtt_publish_param param;
	param.message.topic.qos = tx_data_pub.qos;
	param.message.topic.topic.utf8 = CONFIG_AWS_TOPIC_NAME;
	param.message.topic.topic.size = strlen(CONFIG_AWS_TOPIC_NAME);
	param.message.payload.data = tx_data_pub.ptr;
	param.message.payload.len = tx_data_pub.len;
	param.dup_flag = tx_data_pub.dup_flag;
	param.retain_flag = tx_data_pub.retain_flag;
	if (tx_data_pub.message_id == 0)
	{
		param.message_id = k_cycle_get_32();
		LOG_DBG("Using message ID %d set by the library", param.message_id);
	}
	else
	{
		param.message_id = tx_data_pub.message_id;
		LOG_DBG("Using message ID %d set by the application", param.message_id);
	}
	LOG_DBG("Publishing to topic: %s", (char *)param.message.topic.topic.utf8);
	return mqtt_publish(&client, &param);
}
int aws_iot_disconnect(void)
{
	atomic_set(&disconnect_requested, 1);
	return mqtt_disconnect(&client);
}
int aws_iot_connect(struct aws_iot_config *const config)
{
	int err;
	if (IS_ENABLED(CONFIG_AWS_IOT_CONNECTION_POLL_THREAD))
	{
		err = connection_poll_start();
		if (err)
		{
			LOG_WRN("connection_poll_start failed, error: %d", err);
			return err;
		}
	}
	else
	{
		atomic_set(&disconnect_requested, 0);
		err = connect_client(config);
		if (err)
		{
			LOG_WRN("connect_client failed, error: %d", err);
			return err;
		}
		atomic_set(&aws_iot_disconnected, 0);
	}
	return 0;
}
int aws_iot_init(const struct aws_iot_config *const config, aws_iot_evt_handler_t event_handler)
{
	int err;
  
	err = aws_iot_set_host_name(cattle_host_name_buf, strlen(cattle_host_name_buf));
    if (err) {
        LOG_ERR("aws_iot_set_host_name, error: %d", err);
        return err;
    }
	if ((IS_ENABLED(CONFIG_AWS_IOT_CLIENT_ID_APP) ||
		 IS_ENABLED(CONFIG_AWS_IOT_BROKER_HOST_NAME_APP)) &&
		config == NULL)
	{
		LOG_ERR("config is NULL");
		return -EINVAL;
	}
	if (IS_ENABLED(CONFIG_AWS_IOT_CLIENT_ID_APP) &&
		config->client_id_len >= CONFIG_AWS_IOT_CLIENT_ID_MAX_LEN)
	{
		LOG_ERR("Client ID string too long");
		return -EMSGSIZE;
	}
	if (IS_ENABLED(CONFIG_AWS_IOT_CLIENT_ID_APP) &&
		config->client_id == NULL)
	{
		LOG_ERR("Client ID not set in the application");
		return -ENODATA;
	}
	if (IS_ENABLED(CONFIG_AWS_IOT_BROKER_HOST_NAME_APP) &&
		config->host_name_len >= CONFIG_AWS_IOT_BROKER_HOST_NAME_MAX_LEN)
	{
		LOG_ERR("AWS host name string too long");
		return -EMSGSIZE;
	}
	if (IS_ENABLED(CONFIG_AWS_IOT_BROKER_HOST_NAME_APP) &&
		config->host_name == NULL)
	{
		LOG_ERR("AWS host name not set in the application");
		return -ENODATA;
	}
#if defined(CONFIG_AWS_IOT_BROKER_HOST_NAME_APP)
	err = aws_iot_set_host_name(config->host_name, config->host_name_len);
#else
	err = aws_iot_set_host_name(CONFIG_AWS_IOT_BROKER_HOST_NAME,
								strlen(CONFIG_AWS_IOT_BROKER_HOST_NAME));
#endif 
	if (err)
	{
		LOG_ERR("aws_iot_set_host_name, error: %d", err);
		return err;
	}

	module_evt_handler = event_handler;
	return err;
}

void fetch_time_from_sntp(int *epochArr)
{
    struct sntp_time sntp_time;
    int ret;
    #define NTP_SERVER_IP "216.239.35.0"

    printk("Fetching SNTP from %s\n", NTP_SERVER_IP);
    // if (!atomic_get(&fota_active)) {           // ← ADD THIS
    //     printk("Fetching SNTP from %s\n", NTP_SERVER_IP);
    // }


    ret = sntp_simple(NTP_SERVER_IP, 10000, &sntp_time);
    if (ret < 0)
    {
        LOG_ERR("sntp_simple failed: %d", ret);
        return;
    }

    time_t fetchedTime = (time_t)sntp_time.seconds;
    uint64_t maxTime = 1000000000;

    for (int epochIdx = 0; epochIdx < 10; epochIdx++)
    {
        epochArr[epochIdx] = ((fetchedTime / maxTime) % 10);
        maxTime /= 10;
    }
    
    LOG_INF("EpochTime:%lld", fetchedTime);
    printk("SNTP sync successful! epoch[0]=%d\n", epochArr[0]);
}
int aws_iot_subscription_topics_add(const struct aws_iot_topic_data *const topic_list,size_t list_count)
{
    if ((topic_list == NULL) || (list_count == 0)) {
        return -EINVAL;
    }
    if ((list_count + app_topic_data.list_count) >
         CONFIG_AWS_IOT_APP_SUBSCRIPTION_LIST_COUNT) {
        return -ENOMEM;
    }
    for (size_t i = 0; i < list_count; i++) {
        app_topic_data.list[app_topic_data.list_count].topic.utf8 = topic_list[i].str;
        app_topic_data.list[app_topic_data.list_count].topic.size = topic_list[i].len;
        app_topic_data.list[app_topic_data.list_count].qos = CONFIG_AWS_QOS;
        app_topic_data.list_count++;
    }
    return 0;
}



#if defined(CONFIG_AWS_IOT_CONNECTION_POLL_THREAD)



void aws_iot_cloud_poll(void)
{
    int err;
    struct pollfd fds[2];
    struct aws_iot_evt aws_iot_evt = {
        .type = AWS_IOT_EVT_DISCONNECTED,
        .data = {.err = AWS_IOT_DISCONNECT_MISC}
    };

start:
    k_sem_take(&connection_poll_sem, K_FOREVER);
    while (k_sem_take(&connection_poll_sem, K_NO_WAIT) == 0) { }

    if (atomic_get(&fota_active)) {
        LOG_INF("FOTA active at poll start, waiting...");
        k_sleep(K_SECONDS(5));
        goto start;
    }

    /* ── NEW: Wait for GSM before doing ANYTHING ── */
    extern bool is_network_connected;
    {
        int gsm_wait = 0;
        while (!is_network_connected && gsm_wait < 90) {
            LOG_INF("Poll thread: waiting for GSM... %d/90", gsm_wait + 1);
            k_sleep(K_SECONDS(1));
            gsm_wait++;
        }
        if (!is_network_connected) {
            LOG_ERR("Poll thread: GSM never came up, retrying in 15s");
            k_sleep(K_SECONDS(15));
            k_sem_give(&connection_poll_sem);
            goto start;
        }
        LOG_INF("Poll thread: GSM ready, proceeding to SNTP + connect");
    }

    extern int epochTime[10];

    /* ── SNTP: only once per boot, with proper delays ── */
    if (!sntp_synced) {
        for (int i = 0; i < 5 && epochTime[0] == 0; i++) {
            LOG_WRN("SNTP attempt %d/5", i + 1);
            fetch_time_from_sntp(epochTime);
            if (epochTime[0] != 0) {
                sntp_synced = true;
                break;
            }
            k_sleep(K_SECONDS(5));
        }
        if (!sntp_synced) {
            LOG_WRN("SNTP unavailable, proceeding anyway");
            epochTime[0] = 1;
            sntp_synced = true;
        }
    }

    atomic_set(&connection_poll_active, 1);

    /* ── Connect DMS (optional, non-fatal) ── */
    err = connect_client_dms();
    if (err) {
        LOG_WRN("DMS Connect failed (%d), continuing without DMS", err);
    }

    k_sleep(K_MSEC(2000));

    /* ── Connect Cattle/AWS (mandatory) ── */
    err = connect_client(NULL);
    if (err) {
        LOG_ERR("Cattle (AWS) Connect failed: %d", err);
        goto reset;
    }

    LOG_INF("AWS Cattle connected. DMS=%s",
            (client_dms.transport.tls.sock >= 0) ? "OK" : "FAILED");

    atomic_set(&aws_iot_disconnected, 0);

    /* ── Main poll loop ── */
    while (true) {

        if (atomic_get(&fota_active)) {
            k_sleep(K_SECONDS(2));
            continue;
        }

        int cattle_sock = client.transport.tls.sock;
        if (cattle_sock < 0) {
            LOG_ERR("Cattle socket invalid, reconnecting");
            break;
        }

        fds[0].fd     = cattle_sock;
        fds[0].events = POLLIN;
        fds[1].fd     = client_dms.transport.tls.sock;
        fds[1].events = POLLIN;

        int nfds    = (client_dms.transport.tls.sock >= 0) ? 2 : 1;
        int timeout = aws_iot_keepalive_time_left();

        err = poll(fds, nfds, timeout);

        if (err == 0) {
            err = aws_iot_ping();
            if (err) {
                LOG_ERR("Cattle ping failed: %d", err);
                break;
            }
            if (nfds == 2) {
                mqtt_ping(&client_dms);
            }
            continue;
        }

        if (err < 0) {
            LOG_ERR("Poll error: %d errno: %d", err, errno);
            break;
        }

        if (fds[0].revents & POLLIN) {
            err = aws_iot_input();
            if (err) {
                LOG_ERR("Cattle input err: %d", err);
                break;
            }
            if (atomic_get(&aws_iot_disconnected) == 1) {
                break;
            }
        }

        if (nfds == 2 && (fds[1].revents & POLLIN)) {
            err = mqtt_input(&client_dms);
            if (err) {
                LOG_WRN("DMS input err: %d, disabling DMS", err);
                mqtt_disconnect(&client_dms);
                client_dms.transport.tls.sock = -1;
            }
        }

        if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
            LOG_ERR("Cattle socket error flags: 0x%x, reconnecting",
                    fds[0].revents);
            break;
        }

        if (nfds == 2 && (fds[1].revents & (POLLERR | POLLHUP | POLLNVAL))) {
            LOG_WRN("DMS socket error, disabling DMS");
            mqtt_disconnect(&client_dms);
            client_dms.transport.tls.sock = -1;
        }

    } /* end while(true) */

    if (atomic_get(&aws_iot_disconnected) == 0) {
        aws_iot_notify_event(&aws_iot_evt);
        aws_iot_disconnect();
    }

    if (atomic_get(&fota_active)) {
        LOG_INF("Poll exited due to FOTA, waiting...");
        while (atomic_get(&fota_active)) { k_sleep(K_SECONDS(2)); }
        atomic_set(&connection_poll_active, 0);
        k_sem_take(&connection_poll_sem, K_NO_WAIT);
        k_sleep(K_SECONDS(3));
        k_sem_give(&connection_poll_sem);
        goto start;
    }

reset:
    {
        int s;
        s = client_dms.transport.tls.sock;
        if (s >= 0) { mqtt_disconnect(&client_dms); k_sleep(K_MSEC(100)); close(s); }
        client_dms.transport.tls.sock = -1;

        s = client.transport.tls.sock;
        if (s >= 0) { mqtt_disconnect(&client); k_sleep(K_MSEC(100)); close(s); }
        client.transport.tls.sock = -1;
    }
    atomic_set(&connection_poll_active, 0);
    k_sem_take(&connection_poll_sem, K_NO_WAIT);
    while (atomic_get(&fota_active)) {
        LOG_INF("FOTA in progress, holding reconnection...");
        k_sleep(K_SECONDS(2));
    }
    LOG_INF("Waiting 20s before reconnect to allow modem recovery...");
    k_sleep(K_SECONDS(20));
    k_sem_give(&connection_poll_sem);
    goto start;
}







K_THREAD_DEFINE(aws_connection_poll_thread,
                CONFIG_AWS_IOT_POLL_THREAD_STACK_SIZE,
                aws_iot_cloud_poll, NULL, NULL, NULL,
                K_PRIO_PREEMPT(0), 0, 0);
#endif


Device: nRF5340 DK (nrf5340dk_nrf5340_cpuapp) SDK Version: nRF Connect SDK v2.5.1 Modem: Quectel EC200U over PPP (uart@9000)

Description

The device successfully receives the complete pre-signed AWS S3 URL from the DMS server (path length 1852 characters, confirmed in logs). The same URL downloads the firmware file successfully when tested in Postman. However, when the device attempts to download using fota_download_start(), AWS S3 returns HTTP 403 Forbidden.

Error chain on device

<err> download_client: Unexpected HTTP response: 403 forbidden
<err> fota_download: Download client error
<err> aws: FOTA download error

Most likely causes based on Nordic SDK documentation

  1. Range requests causing signature mismatch: With CONFIG_DOWNLOAD_CLIENT_RANGE_REQUESTS=y, the download_client sends a ranged HTTP GET with a Range: bytes=X-Y header, expecting HTTP 206. AWS S3 pre-signed URLs are signed for specific headers — if the URL was signed only with X-Amz-SignedHeaders=host, adding a Range header causes S3 to reject the request with 403 because the request no longer matches the signature. [

  2. Missing Content-Range in S3 response: The download_client library requires a Content-Range header in the HTTP response when using range requests. If S3 does not return this header, the download fails. 

Steps already confirmed

  • Full pre-signed URL (1852 chars) is correctly received and passed to fota_download_start()
  • TLS connection to S3 establishes successfully
  • Same URL works in Postman — confirms the URL is valid and not expired at the time of the device attempt
Parents Reply Children
No Data
Related