nimble/ll: Add support for Channel Map Update in periodic scanner
diff --git a/nimble/controller/include/controller/ble_ll.h b/nimble/controller/include/controller/ble_ll.h
index 1c43e69..24d7db3 100644
--- a/nimble/controller/include/controller/ble_ll.h
+++ b/nimble/controller/include/controller/ble_ll.h
@@ -444,6 +444,14 @@
 #define BLE_LL_ADDR_SUBTYPE_RPA         (1)
 #define BLE_LL_ADDR_SUBTYPE_NRPA        (2)
 
+/* ACAD data types */
+#define BLE_LL_ACAD_CHANNEL_MAP_UPDATE_IND 0x28
+
+struct ble_ll_acad_channel_map_update_ind {
+    uint8_t map[5];
+    uint16_t instant;
+} __attribute__((packed));
+
 /*--- External API ---*/
 /* Initialize the Link Layer */
 void ble_ll_init(void);
diff --git a/nimble/controller/src/ble_ll_sync.c b/nimble/controller/src/ble_ll_sync.c
index df80608..7c46d10 100644
--- a/nimble/controller/src/ble_ll_sync.c
+++ b/nimble/controller/src/ble_ll_sync.c
@@ -60,6 +60,7 @@
 #define BLE_LL_SYNC_SM_FLAG_DISABLED        0x0040
 #define BLE_LL_SYNC_SM_FLAG_ADDR_RESOLVED   0x0080
 #define BLE_LL_SYNC_SM_FLAG_HCI_TRUNCATED   0x0100
+#define BLE_LL_SYNC_SM_FLAG_NEW_CHANMAP     0x0200
 
 #define BLE_LL_SYNC_CHMAP_LEN               5
 #define BLE_LL_SYNC_ITVL_USECS              1250
@@ -75,6 +76,9 @@
     uint8_t chanmap[BLE_LL_SYNC_CHMAP_LEN];
     uint8_t num_used_chans;
 
+    uint8_t chanmap_new[BLE_LL_SYNC_CHMAP_LEN];
+    uint16_t chanmap_new_instant;
+
     uint8_t chan_index;
     uint8_t chan_chain;
 
@@ -525,7 +529,8 @@
 }
 
 static int
-ble_ll_sync_parse_ext_hdr(struct os_mbuf *om, uint8_t **aux, int8_t *tx_power)
+ble_ll_sync_parse_ext_hdr(struct os_mbuf *om, uint8_t **aux, int8_t *tx_power,
+                          uint8_t **acad, uint8_t *acad_len)
 {
     uint8_t *rxbuf = om->om_data;
     uint8_t ext_hdr_flags;
@@ -585,12 +590,17 @@
             i += BLE_LL_EXT_ADV_TX_POWER_SIZE;
         }
 
-        /* TODO Handle ACAD if needed */
-
         /* sanity check */
         if (i > ext_hdr_len) {
             return -1;
         }
+
+        /* ACAD */
+        if (ext_hdr_len > (i + 1)) {
+            *acad = ext_hdr + i;
+            *acad_len = ext_hdr_len - i - 1;
+        }
+
     }
 
     return pdu_len - ext_hdr_len - 1;
@@ -996,6 +1006,70 @@
     ble_ll_sync_est_event_failed(BLE_ERR_CONN_ESTABLISHMENT);
 }
 
+static bool
+ble_ll_sync_check_acad(struct ble_ll_sync_sm *sm,
+                       const uint8_t *acad, uint8_t acad_len)
+{
+    const struct ble_ll_acad_channel_map_update_ind *chmu;
+    unsigned int ad_len;
+    uint8_t ad_type;
+
+    /* assume no empty fields */
+    while (acad_len > 2) {
+        ad_len = acad[0];
+        ad_type = acad[1];
+
+        /* early termination should not happen in ACAD */
+        if (ad_len == 0) {
+            return false;
+        }
+
+        /* check if not passing pass acad data */
+        if (ad_len + 1 > acad_len) {
+            return false;
+        }
+
+        switch (ad_type) {
+        case BLE_LL_ACAD_CHANNEL_MAP_UPDATE_IND:
+            chmu = (const void *)&acad[2];
+
+            if (ad_len - 1 != sizeof(*chmu)) {
+                return false;
+            }
+
+            /* Channels Mask (37 bits)
+             * TODO should we check this?
+             */
+            sm->chanmap_new[0] = chmu->map[0];
+            sm->chanmap_new[1] = chmu->map[1];
+            sm->chanmap_new[2] = chmu->map[2];
+            sm->chanmap_new[3] = chmu->map[3];
+            sm->chanmap_new[4] = chmu->map[4] & 0x1f;
+
+            /* drop if channel map is invalid */
+            if (ble_ll_utils_calc_num_used_chans(sm->chanmap_new) == 0) {
+                return false;
+            }
+
+            sm->chanmap_new_instant = le16toh(chmu->instant);
+            sm->flags |= BLE_LL_SYNC_SM_FLAG_NEW_CHANMAP;
+            break;
+        default:
+            break;
+        }
+
+        acad += ad_len + 1;
+        acad_len -= ad_len + 1;
+    }
+
+    /* should have no trailing zeros */
+    if (acad_len) {
+        return false;
+    }
+
+    return true;
+}
+
 void
 ble_ll_sync_rx_pkt_in(struct os_mbuf *rxpdu, struct ble_mbuf_hdr *hdr)
 {
@@ -1003,7 +1077,10 @@
     bool aux_scheduled = false;
     int8_t tx_power = 127; /* defaults to not available */
     uint8_t *aux = NULL;
+    uint8_t *acad = NULL;
+    uint8_t acad_len;
     int datalen;
+    bool reports_enabled;
 
     BLE_LL_ASSERT(sm);
 
@@ -1048,36 +1125,43 @@
         goto end_event;
     }
 
-    if (ble_ll_hci_is_le_event_enabled(BLE_HCI_LE_SUBEV_PERIODIC_ADV_RPT) &&
-        !(sm->flags & BLE_LL_SYNC_SM_FLAG_DISABLED)) {
-        /* get ext header data */
-        datalen = ble_ll_sync_parse_ext_hdr(rxpdu, &aux, &tx_power);
-        if (datalen < 0) {
-            /* we got bad packet, end event */
-            goto end_event;
-        }
+    /* get ext header data */
+    datalen = ble_ll_sync_parse_ext_hdr(rxpdu, &aux, &tx_power, &acad, &acad_len);
+    if (datalen < 0) {
+        /* we got bad packet, end event */
+        goto end_event;
+    }
 
+    reports_enabled = ble_ll_hci_is_le_event_enabled(BLE_HCI_LE_SUBEV_PERIODIC_ADV_RPT) &&
+                      !(sm->flags & BLE_LL_SYNC_SM_FLAG_DISABLED);
+
+    /* no need to schedule for chain if reporting is disabled */
+    if (reports_enabled) {
         /* if aux is present, we need to schedule ASAP */
         if (aux && (ble_ll_sync_schedule_chain(sm, hdr, aux) == 0)) {
             aux_scheduled = true;
         }
+    }
 
-        /* in case data reporting is enabled we need to send sync established here */
-        if (sm->flags & BLE_LL_SYNC_SM_FLAG_ESTABLISHING) {
-            ble_ll_sync_established(sm);
-        }
+    /* check ACAD, needs to be done before rxpdu is adjusted for ADV data  */
+    if (acad && !ble_ll_sync_check_acad(sm, acad, acad_len)) {
+        /* we got bad packet (bad ACAD data), end event */
+        goto end_event;
+    }
 
+    /* we need to establish link even if reporting was disabled */
+    if (sm->flags & BLE_LL_SYNC_SM_FLAG_ESTABLISHING) {
+        ble_ll_sync_established(sm);
+    }
+
+    /* only if reporting is enabled */
+    if (reports_enabled) {
         /* Adjust rxpdu to contain advertising data only */
         ble_ll_sync_adjust_ext_hdr(rxpdu);
 
         /* send reports from this PDU */
         ble_ll_sync_send_per_adv_rpt(sm, rxpdu, hdr->rxinfo.rssi, tx_power,
                                      datalen, aux, aux_scheduled);
-    } else {
-        /* we need to establish link even if reporting was disabled */
-        if (sm->flags & BLE_LL_SYNC_SM_FLAG_ESTABLISHING) {
-            ble_ll_sync_established(sm);
-        }
     }
 
     /* if chain was scheduled we don't end event yet */
@@ -1131,6 +1215,20 @@
     /* Set event counter to the next event */
     sm->event_cntr += 1 + skip;
 
+    /* update channel map if needed */
+    if (sm->flags & BLE_LL_SYNC_SM_FLAG_NEW_CHANMAP) {
+        if (((int16_t)(sm->event_cntr - sm->chanmap_new_instant)) >= 0) {
+            /* map was verified on reception */
+            sm->chanmap[0] = sm->chanmap_new[0];
+            sm->chanmap[1] = sm->chanmap_new[1];
+            sm->chanmap[2] = sm->chanmap_new[2];
+            sm->chanmap[3] = sm->chanmap_new[3];
+            sm->chanmap[4] = sm->chanmap_new[4];
+            sm->num_used_chans = ble_ll_utils_calc_num_used_chans(sm->chanmap);
+            sm->flags &= ~BLE_LL_SYNC_SM_FLAG_NEW_CHANMAP;
+        }
+    }
+
     /* Calculate channel index of next event */
     sm->chan_index = ble_ll_utils_calc_dci_csa2(sm->event_cntr, sm->channel_id,
                                                 sm->num_used_chans, sm->chanmap);