[mac] tracking supported channel mask (#2838)

This commit contains the following changes:
- Adds new logic to `Mac` to track supported channel mask along
  get/set methods.
- Adds public OT APIs `otLinkGetSupportedChannelMask()` and
 `otLinkSetSupportedChannelMask()` to get/set the channel mask.
- Ensures supported channel mask from `Mac` is included in Dataset
  TLVs when a local Dataset is generated.
- Ensures the channel mask at `Mac` gets updated when a new Dataset
  configuration is applied.
- Adds `OT_CHANGED` notification for signaling channel mask changes.
- Updates `NcpBase` and `PHY_CHAN_SUPPORTED` spinel property get/set
  handlers to adopt the new public APIs.
- Updates `NcpBase` to emit async `PHY_CHAN_SUPPORTED` updated on
  OT_CHANGED channel mask notification.
diff --git a/include/openthread/instance.h b/include/openthread/instance.h
index dc3f88d..3f18782 100644
--- a/include/openthread/instance.h
+++ b/include/openthread/instance.h
@@ -248,6 +248,7 @@
     OT_CHANGED_PSKC                        = 1 << 21, ///< PSKc changed
     OT_CHANGED_SECURITY_POLICY             = 1 << 22, ///< Security Policy changed
     OT_CHANGED_CHANNEL_MANAGER_NEW_CHANNEL = 1 << 23, ///< Channel Manager new pending Thread channel changed
+    OT_CHANGED_SUPPORTED_CHANNEL_MASK      = 1 << 24, ///< Supported channel mask changed
 };
 
 /**
diff --git a/include/openthread/link.h b/include/openthread/link.h
index 126b97e..6d69080 100644
--- a/include/openthread/link.h
+++ b/include/openthread/link.h
@@ -189,7 +189,7 @@
  * @param[in]  aChannel    The IEEE 802.15.4 channel.
  *
  * @retval  OT_ERROR_NONE           Successfully set the channel.
- * @retval  OT_ERROR_INVALID_ARGS   If @p aChnanel is not in the range [11, 26].
+ * @retval  OT_ERROR_INVALID_ARGS   If @p aChannel is not in the range [11, 26] or is not in the supported channel mask.
  * @retval  OT_ERROR_INVALID_STATE  Thread protocols are enabled.
  *
  * @sa otLinkGetChannel
@@ -198,6 +198,30 @@
 OTAPI otError OTCALL otLinkSetChannel(otInstance *aInstance, uint8_t aChannel);
 
 /**
+ * Get the supported channel mask.
+ *
+ * @param[in] aInstance A pointer to an OpenThread instance.
+ *
+ * @returns The supported channel mask as `uint32_t` with bit 0 (lsb) mapping to channel 0, bit 1 to channel 1, so on.
+ *
+ */
+uint32_t otLinkGetSupportedChannelMask(otInstance *aInstance);
+
+/**
+ * Set the supported channel mask.
+ *
+ * This function succeeds only when Thread protocols are disabled.
+ *
+ * @param[in]  aInstance     A pointer to an OpenThread instance.
+ * @param[in]  aChannelMask  The supported channel mask (bit 0 or lsb mapping to channel 0, and so on).
+ *
+ * @retval  OT_ERROR_NONE           Successfully set the supported channel mask.
+ * @retval  OT_ERROR_INVALID_STATE  Thread protocols are enabled.
+ *
+ */
+otError otLinkSetSupportedChannelMask(otInstance *aInstance, uint32_t aChannelMask);
+
+/**
  * Get the IEEE 802.15.4 Extended Address.
  *
  * @param[in]  aInstance A pointer to an OpenThread instance.
diff --git a/src/core/api/link_api.cpp b/src/core/api/link_api.cpp
index e5a1da6..7b71caf 100644
--- a/src/core/api/link_api.cpp
+++ b/src/core/api/link_api.cpp
@@ -65,6 +65,27 @@
     return error;
 }
 
+uint32_t otLinkGetSupportedChannelMask(otInstance *aInstance)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.GetThreadNetif().GetMac().GetSupportedChannelMask().GetMask();
+}
+
+otError otLinkSetSupportedChannelMask(otInstance *aInstance, uint32_t aChannelMask)
+{
+    otError   error    = OT_ERROR_NONE;
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    VerifyOrExit(instance.GetThreadNetif().GetMle().GetRole() == OT_DEVICE_ROLE_DISABLED,
+                 error = OT_ERROR_INVALID_STATE);
+
+    instance.GetThreadNetif().GetMac().SetSupportedChannelMask(aChannelMask);
+
+exit:
+    return error;
+}
+
 const otExtAddress *otLinkGetExtendedAddress(otInstance *aInstance)
 {
     Instance &instance = *static_cast<Instance *>(aInstance);
diff --git a/src/core/common/notifier.cpp b/src/core/common/notifier.cpp
index 70ac44a..da772e6 100644
--- a/src/core/common/notifier.cpp
+++ b/src/core/common/notifier.cpp
@@ -328,6 +328,10 @@
         retval = "CMNewChan";
         break;
 
+    case OT_CHANGED_SUPPORTED_CHANNEL_MASK:
+        retval = "ChanMask";
+        break;
+
     default:
         break;
     }
diff --git a/src/core/mac/mac.cpp b/src/core/mac/mac.cpp
index 4e97a64..566d7a8 100644
--- a/src/core/mac/mac.cpp
+++ b/src/core/mac/mac.cpp
@@ -170,6 +170,7 @@
     , mPanChannel(OPENTHREAD_CONFIG_DEFAULT_CHANNEL)
     , mRadioChannel(OPENTHREAD_CONFIG_DEFAULT_CHANNEL)
     , mRadioChannelAcquisitionId(0)
+    , mSupportedChannelMask(OT_RADIO_SUPPORTED_CHANNELS)
     , mSendHead(NULL)
     , mSendTail(NULL)
     , mReceiveHead(NULL)
@@ -249,7 +250,14 @@
     mScanContext  = aContext;
     mScanDuration = aScanDuration;
     mScanChannel  = ChannelMask::kChannelIteratorFirst;
-    mScanChannelMask.SetMask((aScanChannels == 0) ? static_cast<uint32_t>(kScanChannelsAll) : aScanChannels);
+
+    if (aScanChannels == 0)
+    {
+        aScanChannels = OT_RADIO_SUPPORTED_CHANNELS;
+    }
+
+    mScanChannelMask.SetMask(aScanChannels);
+    mScanChannelMask.Intersect(mSupportedChannelMask);
     StartOperation(aScanOperation);
 }
 
@@ -513,6 +521,7 @@
     otError error = OT_ERROR_NONE;
 
     VerifyOrExit(OT_RADIO_CHANNEL_MIN <= aChannel && aChannel <= OT_RADIO_CHANNEL_MAX, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(mSupportedChannelMask.ContainsChannel(aChannel), error = OT_ERROR_INVALID_ARGS);
 
     VerifyOrExit(mPanChannel != aChannel, GetNotifier().SignalIfFirst(OT_CHANGED_THREAD_CHANNEL));
 
@@ -536,6 +545,8 @@
     otError error = OT_ERROR_NONE;
 
     VerifyOrExit(OT_RADIO_CHANNEL_MIN <= aChannel && aChannel <= OT_RADIO_CHANNEL_MAX, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(mSupportedChannelMask.ContainsChannel(aChannel), error = OT_ERROR_INVALID_ARGS);
+
     VerifyOrExit(mRadioChannelAcquisitionId && aAcquisitionId == mRadioChannelAcquisitionId,
                  error = OT_ERROR_INVALID_STATE);
 
@@ -577,6 +588,20 @@
     return error;
 }
 
+void Mac::SetSupportedChannelMask(const ChannelMask &aMask)
+{
+    ChannelMask newMask = aMask;
+
+    newMask.Intersect(OT_RADIO_SUPPORTED_CHANNELS);
+    VerifyOrExit(newMask != mSupportedChannelMask, GetNotifier().SignalIfFirst(OT_CHANGED_SUPPORTED_CHANNEL_MASK));
+
+    mSupportedChannelMask = newMask;
+    GetNotifier().Signal(OT_CHANGED_SUPPORTED_CHANNEL_MASK);
+
+exit:
+    return;
+}
+
 otError Mac::SetNetworkName(const char *aNetworkName)
 {
     return SetNetworkName(aNetworkName, OT_NETWORK_NAME_MAX_SIZE + 1);
diff --git a/src/core/mac/mac.hpp b/src/core/mac/mac.hpp
index 54a3a13..16cb934 100644
--- a/src/core/mac/mac.hpp
+++ b/src/core/mac/mac.hpp
@@ -598,7 +598,8 @@
      *
      * @param[in]  aChannel  The IEEE 802.15.4 PAN Channel.
      *
-     * @retval OT_ERROR_NONE  Successfully set the IEEE 802.15.4 PAN Channel.
+     * @retval OT_ERROR_NONE           Successfully set the IEEE 802.15.4 PAN Channel.
+     * @retval OT_ERROR_INVALID_ARGS   The @p aChannel is not in the supported channel mask.
      *
      */
     otError SetPanChannel(uint8_t aChannel);
@@ -617,7 +618,9 @@
      *
      * @param[in]  aChannel  The IEEE 802.15.4 Radio Channel.
      *
-     * @retval OT_ERROR_NONE  Successfully set the IEEE 802.15.4 Radio Channel.
+     * @retval OT_ERROR_NONE           Successfully set the IEEE 802.15.4 Radio Channel.
+     * @retval OT_ERROR_INVALID_ARGS   The @p aChannel is not in the supported channel mask.
+     * @retval OT_ERROR_INVALID_STATE  The acquisition ID is incorrect.
      *
      */
     otError SetRadioChannel(uint16_t aAcquisitionId, uint8_t aChannel);
@@ -644,6 +647,22 @@
     otError ReleaseRadioChannel(void);
 
     /**
+     * This method returns the supported channel mask.
+     *
+     * @returns The supported channel mask.
+     *
+     */
+    const ChannelMask &GetSupportedChannelMask(void) const { return mSupportedChannelMask; }
+
+    /**
+     * This method sets the supported channel mask
+     *
+     * @param[in] aMask   The supported channel mask.
+     *
+     */
+    void SetSupportedChannelMask(const ChannelMask &aMask);
+
+    /**
      * This method returns the IEEE 802.15.4 Network Name.
      *
      * @returns A pointer to the IEEE 802.15.4 Network Name.
@@ -1024,6 +1043,7 @@
     uint8_t      mPanChannel;
     uint8_t      mRadioChannel;
     uint16_t     mRadioChannelAcquisitionId;
+    ChannelMask  mSupportedChannelMask;
 
     otNetworkName   mNetworkName;
     otExtendedPanId mExtendedPanId;
diff --git a/src/core/meshcop/dataset.cpp b/src/core/meshcop/dataset.cpp
index 8c1aae5..f8f875a 100644
--- a/src/core/meshcop/dataset.cpp
+++ b/src/core/meshcop/dataset.cpp
@@ -550,6 +550,18 @@
             break;
         }
 
+        case Tlv::kChannelMask:
+        {
+            const ChannelMask0Entry *mask0Entry = static_cast<const ChannelMaskTlv *>(cur)->GetMask0Entry();
+
+            if (mask0Entry != NULL)
+            {
+                mac.SetSupportedChannelMask(mask0Entry->GetMask());
+            }
+
+            break;
+        }
+
         case Tlv::kPanId:
             mac.SetPanId(static_cast<const PanIdTlv *>(cur)->GetPanId());
             break;
diff --git a/src/core/meshcop/dataset_manager_ftd.cpp b/src/core/meshcop/dataset_manager_ftd.cpp
index 091b0f1..b3df342 100644
--- a/src/core/meshcop/dataset_manager_ftd.cpp
+++ b/src/core/meshcop/dataset_manager_ftd.cpp
@@ -618,7 +618,7 @@
     {
         ChannelMask0Tlv tlv;
         tlv.Init();
-        tlv.SetMask(OT_RADIO_SUPPORTED_CHANNELS);
+        tlv.SetMask(netif.GetMac().GetSupportedChannelMask().GetMask());
         dataset.Set(tlv);
     }
 
diff --git a/src/ncp/changed_props_set.cpp b/src/ncp/changed_props_set.cpp
index 59540ba..55bf24d 100644
--- a/src/ncp/changed_props_set.cpp
+++ b/src/ncp/changed_props_set.cpp
@@ -45,7 +45,7 @@
 // number of entries in the list is always less than or equal to 32.
 //
 const ChangedPropsSet::Entry ChangedPropsSet::mSupportedProps[] = {
-    // Spinel property                                  Status (if prop is `LAST_STATUS`)  IsFilterable?
+    // Spinel property , Status (if prop is `LAST_STATUS`),  IsFilterable?
 
     {SPINEL_PROP_LAST_STATUS, SPINEL_STATUS_RESET_UNKNOWN, false},    // 0
     {SPINEL_PROP_STREAM_DEBUG, SPINEL_STATUS_OK, true},               // 1
@@ -79,8 +79,9 @@
     {SPINEL_PROP_NET_XPANID, SPINEL_STATUS_OK, true},                   // 25
     {SPINEL_PROP_NET_MASTER_KEY, SPINEL_STATUS_OK, true},               // 26
     {SPINEL_PROP_NET_PSKC, SPINEL_STATUS_OK, true},                     // 27
+    {SPINEL_PROP_PHY_CHAN_SUPPORTED, SPINEL_STATUS_OK, true},           // 28
 #if OPENTHREAD_ENABLE_CHANNEL_MANAGER
-    {SPINEL_PROP_CHANNEL_MANAGER_NEW_CHANNEL, SPINEL_STATUS_OK, true}, // 28
+    {SPINEL_PROP_CHANNEL_MANAGER_NEW_CHANNEL, SPINEL_STATUS_OK, true}, // 29
 #endif
 
 };
diff --git a/src/ncp/ncp_base.cpp b/src/ncp/ncp_base.cpp
index 56bd55f..ce101bf 100644
--- a/src/ncp/ncp_base.cpp
+++ b/src/ncp/ncp_base.cpp
@@ -183,11 +183,9 @@
     , mDecoder()
     , mHostPowerStateInProgress(false)
     , mLastStatus(SPINEL_STATUS_OK)
-    , mSupportedChannelMask(OT_RADIO_SUPPORTED_CHANNELS)
-    , mChannelMask(OT_RADIO_SUPPORTED_CHANNELS)
+    , mScanChannelMask(OT_RADIO_SUPPORTED_CHANNELS)
     , mScanPeriod(200)
-    , // ms
-    mDiscoveryScanJoinerFlag(false)
+    , mDiscoveryScanJoinerFlag(false)
     , mDiscoveryScanEnableFiltering(false)
     , mDiscoveryScanPanId(0xffff)
     , mUpdateChangedPropsTask(*aInstance, &NcpBase::UpdateChangedProps, this)
diff --git a/src/ncp/ncp_base.hpp b/src/ncp/ncp_base.hpp
index c3af4b0..29541b2 100644
--- a/src/ncp/ncp_base.hpp
+++ b/src/ncp/ncp_base.hpp
@@ -471,8 +471,7 @@
     };
 
     spinel_status_t mLastStatus;
-    uint32_t        mSupportedChannelMask;
-    uint32_t        mChannelMask;
+    uint32_t        mScanChannelMask;
     uint16_t        mScanPeriod;
     bool            mDiscoveryScanJoinerFlag;
     bool            mDiscoveryScanEnableFiltering;
diff --git a/src/ncp/ncp_base_dispatcher.cpp b/src/ncp/ncp_base_dispatcher.cpp
index 5729c92..7b58fcc 100644
--- a/src/ncp/ncp_base_dispatcher.cpp
+++ b/src/ncp/ncp_base_dispatcher.cpp
@@ -651,6 +651,9 @@
         // MTD (or FTD) Properties (Set Handler)
 
 #if OPENTHREAD_MTD || OPENTHREAD_FTD
+    case SPINEL_PROP_PHY_CHAN_SUPPORTED:
+        handler = &NcpBase::HandlePropertySet<SPINEL_PROP_PHY_CHAN_SUPPORTED>;
+        break;
     case SPINEL_PROP_MAC_DATA_POLL_PERIOD:
         handler = &NcpBase::HandlePropertySet<SPINEL_PROP_MAC_DATA_POLL_PERIOD>;
         break;
diff --git a/src/ncp/ncp_base_mtd.cpp b/src/ncp/ncp_base_mtd.cpp
index 994f040..e3fff2c 100644
--- a/src/ncp/ncp_base_mtd.cpp
+++ b/src/ncp/ncp_base_mtd.cpp
@@ -225,7 +225,19 @@
 
 template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_CHAN_SUPPORTED>(void)
 {
-    return EncodeChannelMask(mSupportedChannelMask);
+    return EncodeChannelMask(otLinkGetSupportedChannelMask(mInstance));
+}
+
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_PHY_CHAN_SUPPORTED>(void)
+{
+    uint32_t newMask = 0;
+    otError  error   = OT_ERROR_NONE;
+
+    SuccessOrExit(error = DecodeChannelMask(newMask));
+    error = otLinkSetSupportedChannelMask(mInstance, newMask);
+
+exit:
+    return error;
 }
 
 template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_RSSI>(void)
@@ -2577,7 +2589,7 @@
 
 template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_SCAN_MASK>(void)
 {
-    return EncodeChannelMask(mChannelMask);
+    return EncodeChannelMask(mScanChannelMask);
 }
 
 template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_SCAN_MASK>(void)
@@ -2586,9 +2598,7 @@
     otError  error   = OT_ERROR_NONE;
 
     SuccessOrExit(error = DecodeChannelMask(newMask));
-    VerifyOrExit((~mSupportedChannelMask & newMask) == 0, error = OT_ERROR_INVALID_ARGS);
-
-    mChannelMask = newMask;
+    mScanChannelMask = newMask;
 
 exit:
     return error;
@@ -2662,7 +2672,7 @@
         else
 #endif // OPENTHREAD_RADIO || OPENTHREAD_ENABLE_RAW_LINK_API
         {
-            error = otLinkActiveScan(mInstance, mChannelMask, mScanPeriod, &HandleActiveScanResult_Jump, this);
+            error = otLinkActiveScan(mInstance, mScanChannelMask, mScanPeriod, &HandleActiveScanResult_Jump, this);
         }
 
         SuccessOrExit(error);
@@ -2677,9 +2687,9 @@
             // Make sure we aren't already scanning and that we have
             // only 1 bit set for the channel mask.
             VerifyOrExit(mCurScanChannel == kInvalidScanChannel, error = OT_ERROR_INVALID_STATE);
-            VerifyOrExit(HasOnly1BitSet(mChannelMask), error = OT_ERROR_INVALID_ARGS);
+            VerifyOrExit(HasOnly1BitSet(mScanChannelMask), error = OT_ERROR_INVALID_ARGS);
 
-            scanChannel     = IndexOfMSB(mChannelMask);
+            scanChannel     = IndexOfMSB(mScanChannelMask);
             mCurScanChannel = (int8_t)scanChannel;
 
             error = otLinkRawEnergyScan(mInstance, scanChannel, mScanPeriod, LinkRawEnergyScanDone);
@@ -2687,14 +2697,14 @@
         else
 #endif // OPENTHREAD_RADIO || OPENTHREAD_ENABLE_RAW_LINK_API
         {
-            error = otLinkEnergyScan(mInstance, mChannelMask, mScanPeriod, &HandleEnergyScanResult_Jump, this);
+            error = otLinkEnergyScan(mInstance, mScanChannelMask, mScanPeriod, &HandleEnergyScanResult_Jump, this);
         }
 
         SuccessOrExit(error);
         break;
 
     case SPINEL_SCAN_STATE_DISCOVER:
-        error = otThreadDiscover(mInstance, mChannelMask, mDiscoveryScanPanId, mDiscoveryScanJoinerFlag,
+        error = otThreadDiscover(mInstance, mScanChannelMask, mDiscoveryScanPanId, mDiscoveryScanJoinerFlag,
                                  mDiscoveryScanEnableFiltering, &HandleActiveScanResult_Jump, this);
 
         SuccessOrExit(error);
@@ -2938,6 +2948,7 @@
         {OT_CHANGED_MASTER_KEY, SPINEL_PROP_NET_MASTER_KEY},
         {OT_CHANGED_PSKC, SPINEL_PROP_NET_PSKC},
         {OT_CHANGED_CHANNEL_MANAGER_NEW_CHANNEL, SPINEL_PROP_CHANNEL_MANAGER_NEW_CHANNEL},
+        {OT_CHANGED_SUPPORTED_CHANNEL_MASK, SPINEL_PROP_PHY_CHAN_SUPPORTED},
     };
 
     VerifyOrExit(mThreadChangedFlags != 0);