[ncp] add support for Joiner Discerner and getting list of joiners (#5137)

This commit adds/updates spinel properties to add Joiner Discerner
support on joiner and commissioner side. It also adds support for
getting the list of joiners on commissioner by implementing property
get handler for spinel property `MESHCOP_COMMISSIONER_JOINERS`.
diff --git a/src/lib/spinel/spinel.c b/src/lib/spinel/spinel.c
index c09fcd5..ae0753f 100644
--- a/src/lib/spinel/spinel.c
+++ b/src/lib/spinel/spinel.c
@@ -1891,6 +1891,10 @@
         ret = "MESHCOP_COMMISSIONER_SESSION_ID";
         break;
 
+    case SPINEL_PROP_MESHCOP_JOINER_DISCERNER:
+        ret = "MESHCOP_JOINER_DISCERNER";
+        break;
+
     case SPINEL_PROP_MESHCOP_COMMISSIONER_ANNOUNCE_BEGIN:
         ret = "MESHCOP_COMMISSIONER_ANNOUNCE_BEGIN";
         break;
diff --git a/src/lib/spinel/spinel.h b/src/lib/spinel/spinel.h
index dcc6e06..0822795 100644
--- a/src/lib/spinel/spinel.h
+++ b/src/lib/spinel/spinel.h
@@ -3112,15 +3112,22 @@
     SPINEL_PROP_MESHCOP_COMMISSIONER_STATE = SPINEL_PROP_MESHCOP__BEGIN + 2,
 
     // Thread Commissioner Joiners
-    /** Format `A(t(E)UL)` - insert or remove only
+    /** Format `A(t(t(E|CX)UL))` - get, insert or remove.
      *
      * Required capability: SPINEL_CAP_THREAD_COMMISSIONER
      *
-     * Data per item is:
+     * Data per array entry is:
      *
-     *  `t(E)` | `t()`: Joiner EUI64. Empty struct indicates any Joiner
-     *  `L`           : Timeout (in seconds) after which the Joiner is automatically removed
-     *  `U`           : PSKd
+     *  `t()` | `t(E)` | `t(CX)` : Joiner info struct (formatting varies).
+     *
+     *   -  `t()` or empty struct indicates any joiner.
+     *   -  `t(E)` specifies the Joiner EUI-64.
+     *   -  `t(CX) specifies Joiner Discerner, `C` is Discerner length (in bits), and `X` is Discerner value.
+     *
+     * The struct is followed by:
+     *
+     *  `L` : Timeout after which to remove Joiner (when written should be in seconds, when read is in milliseconds)
+     *  `U` : PSKd
      *
      * For CMD_PROP_VALUE_REMOVE the timeout and PSKd are optional.
      *
@@ -3143,6 +3150,32 @@
      */
     SPINEL_PROP_MESHCOP_COMMISSIONER_SESSION_ID = SPINEL_PROP_MESHCOP__BEGIN + 5,
 
+    /// Thread Joiner Discerner
+    /** Format `CX`  - Read-write
+     *
+     * Required capability: SPINEL_CAP_THREAD_JOINER
+     *
+     * This property represents a Joiner Discerner.
+     *
+     * The Joiner Discerner is used to calculate the Joiner ID used during commissioning/joining process.
+     *
+     * By default (when a discerner is not provided or cleared), Joiner ID is derived as first 64 bits of the result
+     * of computing SHA-256 over factory-assigned IEEE EUI-64. Note that this is the main behavior expected by Thread
+     * specification.
+     *
+     * Format:
+     *
+     *   'C' : The Joiner Discerner bit length (number of bits).
+     *   `X` : The Joiner Discerner value (64-bit unsigned)  - Only present/applicable when length is non-zero.
+     *
+     * When writing to this property, the length can be set to zero to clear any previously set Joiner Discerner value.
+     *
+     * When reading this property if there is no currently set Joiner Discerner, zero is returned as the length (with
+     * no value field).
+     *
+     */
+    SPINEL_PROP_MESHCOP_JOINER_DISCERNER = SPINEL_PROP_MESHCOP__BEGIN + 6,
+
     SPINEL_PROP_MESHCOP__END = 0x90,
 
     SPINEL_PROP_MESHCOP_EXT__BEGIN = 0x1800,
diff --git a/src/ncp/ncp_base_dispatcher.cpp b/src/ncp/ncp_base_dispatcher.cpp
index 74714ed..7eadb8c 100644
--- a/src/ncp/ncp_base_dispatcher.cpp
+++ b/src/ncp/ncp_base_dispatcher.cpp
@@ -135,10 +135,14 @@
 #if OPENTHREAD_FTD
 #if OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
         OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_MESHCOP_COMMISSIONER_STATE),
+        OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_MESHCOP_COMMISSIONER_JOINERS),
         OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_MESHCOP_COMMISSIONER_PROVISIONING_URL),
         OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_MESHCOP_COMMISSIONER_SESSION_ID),
 #endif
 #endif // OPENTHREAD_FTD
+#if OPENTHREAD_CONFIG_JOINER_ENABLE
+        OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_MESHCOP_JOINER_DISCERNER),
+#endif
 #if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
         OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_SERVER_ALLOW_LOCAL_DATA_CHANGE),
         OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_SERVER_SERVICES),
@@ -404,6 +408,9 @@
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_MESHCOP_COMMISSIONER_PROVISIONING_URL),
 #endif
 #endif // OPENTHREAD_FTD
+#if OPENTHREAD_CONFIG_JOINER_ENABLE
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_MESHCOP_JOINER_DISCERNER),
+#endif
 #if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_SERVER_ALLOW_LOCAL_DATA_CHANGE),
 #endif
diff --git a/src/ncp/ncp_base_ftd.cpp b/src/ncp/ncp_base_ftd.cpp
index 2e62622..38df0d7 100644
--- a/src/ncp/ncp_base_ftd.cpp
+++ b/src/ncp/ncp_base_ftd.cpp
@@ -538,23 +538,72 @@
     return error;
 }
 
+template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MESHCOP_COMMISSIONER_JOINERS>(void)
+{
+    otError      error = OT_ERROR_NONE;
+    uint16_t     iter  = 0;
+    otJoinerInfo joinerInfo;
+
+    while (otCommissionerGetNextJoinerInfo(mInstance, &iter, &joinerInfo) == OT_ERROR_NONE)
+    {
+        SuccessOrExit(error = mEncoder.OpenStruct());
+
+        SuccessOrExit(error = mEncoder.OpenStruct()); // Joiner Id (any, EUI64 or a Joiner Discerner) struct
+
+        switch (joinerInfo.mType)
+        {
+        case OT_JOINER_INFO_TYPE_ANY:
+            break;
+
+        case OT_JOINER_INFO_TYPE_EUI64:
+            SuccessOrExit(error = mEncoder.WriteEui64(joinerInfo.mSharedId.mEui64));
+            break;
+
+        case OT_JOINER_INFO_TYPE_DISCERNER:
+            SuccessOrExit(error = mEncoder.WriteUint8(joinerInfo.mSharedId.mDiscerner.mLength));
+            SuccessOrExit(error = mEncoder.WriteUint64(joinerInfo.mSharedId.mDiscerner.mValue));
+            break;
+        }
+
+        SuccessOrExit(error = mEncoder.CloseStruct());
+
+        SuccessOrExit(error = mEncoder.WriteUint32(joinerInfo.mExpirationTime));
+        SuccessOrExit(error = mEncoder.WriteUtf8(joinerInfo.mPskd.m8));
+
+        SuccessOrExit(error = mEncoder.CloseStruct());
+    }
+
+exit:
+    return error;
+}
+
 template <> otError NcpBase::HandlePropertyInsert<SPINEL_PROP_MESHCOP_COMMISSIONER_JOINERS>(void)
 {
     otError             error = OT_ERROR_NONE;
+    otJoinerDiscerner   discerner;
+    bool                withDiscerner = false;
     const otExtAddress *eui64;
     uint32_t            timeout;
     const char *        psk;
 
     SuccessOrExit(error = mDecoder.OpenStruct());
 
-    if (!mDecoder.IsAllReadInStruct())
+    switch (mDecoder.GetRemainingLengthInStruct())
     {
-        SuccessOrExit(error = mDecoder.ReadEui64(eui64));
-    }
-    else
-    {
-        // Empty struct indicates any Joiner (no EUI64 is given so nullptr is used.).
+    case 0:
+        // Empty struct indicates any joiner
         eui64 = nullptr;
+        break;
+
+    case sizeof(spinel_eui64_t):
+        SuccessOrExit(error = mDecoder.ReadEui64(eui64));
+        break;
+
+    default:
+        SuccessOrExit(error = mDecoder.ReadUint8(discerner.mLength));
+        SuccessOrExit(error = mDecoder.ReadUint64(discerner.mValue));
+        withDiscerner = true;
+        break;
     }
 
     SuccessOrExit(error = mDecoder.CloseStruct());
@@ -562,7 +611,14 @@
     SuccessOrExit(error = mDecoder.ReadUint32(timeout));
     SuccessOrExit(error = mDecoder.ReadUtf8(psk));
 
-    error = otCommissionerAddJoiner(mInstance, eui64, psk, timeout);
+    if (withDiscerner)
+    {
+        error = otCommissionerAddJoinerWithDiscerner(mInstance, &discerner, psk, timeout);
+    }
+    else
+    {
+        error = otCommissionerAddJoiner(mInstance, eui64, psk, timeout);
+    }
 
 exit:
     return error;
@@ -571,23 +627,40 @@
 template <> otError NcpBase::HandlePropertyRemove<SPINEL_PROP_MESHCOP_COMMISSIONER_JOINERS>(void)
 {
     otError             error = OT_ERROR_NONE;
+    otJoinerDiscerner   discerner;
+    bool                withDiscerner = false;
     const otExtAddress *eui64;
 
     SuccessOrExit(error = mDecoder.OpenStruct());
 
-    if (!mDecoder.IsAllReadInStruct())
+    switch (mDecoder.GetRemainingLengthInStruct())
     {
-        SuccessOrExit(error = mDecoder.ReadEui64(eui64));
-    }
-    else
-    {
-        // Empty struct indicates any Joiner (no EUI64 is given so nullptr is used.).
+    case 0:
+        // Empty struct indicates any joiner
         eui64 = nullptr;
+        break;
+
+    case sizeof(spinel_eui64_t):
+        SuccessOrExit(error = mDecoder.ReadEui64(eui64));
+        break;
+
+    default:
+        SuccessOrExit(error = mDecoder.ReadUint8(discerner.mLength));
+        SuccessOrExit(error = mDecoder.ReadUint64(discerner.mValue));
+        withDiscerner = true;
+        break;
     }
 
     SuccessOrExit(error = mDecoder.CloseStruct());
 
-    error = otCommissionerRemoveJoiner(mInstance, eui64);
+    if (withDiscerner)
+    {
+        error = otCommissionerRemoveJoinerWithDiscerner(mInstance, &discerner);
+    }
+    else
+    {
+        error = otCommissionerRemoveJoiner(mInstance, eui64);
+    }
 
 exit:
     return error;
diff --git a/src/ncp/ncp_base_mtd.cpp b/src/ncp/ncp_base_mtd.cpp
index 7dda234..9d1765b 100644
--- a/src/ncp/ncp_base_mtd.cpp
+++ b/src/ncp/ncp_base_mtd.cpp
@@ -1532,7 +1532,47 @@
     return error;
 }
 
-#endif
+template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MESHCOP_JOINER_DISCERNER>(void)
+{
+    otError                  error;
+    const otJoinerDiscerner *discerner = otJoinerGetDiscerner(mInstance);
+
+    if (discerner == nullptr)
+    {
+        SuccessOrExit(error = mEncoder.WriteUint8(0));
+    }
+    else
+    {
+        SuccessOrExit(error = mEncoder.WriteUint8(discerner->mLength));
+        SuccessOrExit(error = mEncoder.WriteUint64(discerner->mValue));
+    }
+
+exit:
+    return error;
+}
+
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MESHCOP_JOINER_DISCERNER>(void)
+{
+    otError           error = OT_ERROR_NONE;
+    otJoinerDiscerner discerner;
+
+    SuccessOrExit(error = mDecoder.ReadUint8(discerner.mLength));
+
+    if (discerner.mLength == 0)
+    {
+        // Clearing any previously set Joiner Discerner
+        error = otJoinerSetDiscerner(mInstance, nullptr);
+        ExitNow();
+    }
+
+    SuccessOrExit(error = mDecoder.ReadUint64(discerner.mValue));
+    error = otJoinerSetDiscerner(mInstance, &discerner);
+
+exit:
+    return error;
+}
+
+#endif // OPENTHREAD_CONFIG_JOINER_ENABLE
 
 template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_IPV6_ML_PREFIX>(void)
 {