[nrfconnect] Added support for user data in Factory Data parser (#24088)

* [nrfconnect] Added support for user data in Factory Data parser

Factory data parser did not contain methods to obtain user data.

- Added two methods: GetUserData to obtain raw user data
and GetUserKey to obtain a single key.
- Improved the FactoryDataParser to read and manage the user data field.
- Improved documentation.

* Restyled by prettier-markdown

Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/docs/guides/nrfconnect_factory_data_configuration.md b/docs/guides/nrfconnect_factory_data_configuration.md
index c05b5a6..7826fd6 100644
--- a/docs/guides/nrfconnect_factory_data_configuration.md
+++ b/docs/guides/nrfconnect_factory_data_configuration.md
@@ -41,6 +41,8 @@
 -   [Enabling factory data support](#enabling-factory-data-support)
 -   [Generating factory data](#generating-factory-data)
     -   [Creating factory data JSON file with the first script](#creating-factory-data-json-file-with-the-first-script)
+    -   [How to set user data](#how-to-set-user-data)
+        -   [How to handle user data](#how-to-handle-user-data)
     -   [Verifying using the JSON Schema tool](#verifying-using-the-json-schema-tool)
         -   [Option 1: Using the php-json-schema tool](#option-1-using-the-php-json-schema-tool)
         -   [Option 2: Using a website validator](#option-2-using-a-website-validator)
@@ -110,7 +112,7 @@
 | `spake2_verifier` |           SPAKE2+ verifier           |    97 B    | byte string  |  mandatory  |                                                                                                                                                                        The SPAKE2+ verifier generated using SPAKE2+ salt, iteration counter, and passcode.                                                                                                                                                                         |
 |  `discriminator`  |            Discriminator             |    2 B     |    uint16    |  mandatory  |                                                                                                                                                   A 12-bit value matching the field of the same name in the setup code. The discriminator is used during the discovery process.                                                                                                                                                    |
 |    `passcode`     |            SPAKE passcode            |    4 B     |    uint32    |  optional   | A pairing passcode is a 27-bit unsigned integer which serves as a proof of possession during the commissioning. Its value must be restricted to the values from `0x0000001` to `0x5F5E0FE` (`00000001` to `99999998` in decimal), excluding the following invalid passcode values: `00000000`, `11111111`, `22222222`, `33333333`, `44444444`, `55555555`, `66666666`, `77777777`, `88888888`, `99999999`, `12345678`, `87654321`. |
-|      `user`       |              User data               |  variable  | JSON string  | max 1024 B  |                                                                          The user data is provided in the JSON format. This parameter is optional and depends on user's or manufacturer's purpose (or both). It is provided as a string from persistent storage and should be parsed in the user application. This data is not used by the Matter stack.                                                                           |
+|      `user`       |              User data               |  variable  | JSON string  | max 1024 B  |                             The user data is provided in the JSON format. This parameter is optional and depends on device manufacturer's purpose. It is provided as a CBOR map type from persistent storage and should be parsed in the user application. This data is not used by the Matter stack. To learn how to work with user data, see [How to set user data](#how-to-set-user-data) section.                              |
 
 ### Factory data format
 
@@ -345,6 +347,89 @@
 > location as an existing file. To allow overwriting, add the `--overwrite`
 > option to the argument list of the Python script.
 
+### How to set user data
+
+The user data is an optional field provided in the factory data JSON file and
+depends on the manufacturer's purpose. The `user` field in a JSON factory data
+file is represented by a flat JSON map and it can consist of `string` or `int32`
+data types only. On the device side, the `user` data will be available as a CBOR
+map containing all defined `string` and `int32` fields.
+
+To add user data as an argument to the
+[generate_nrfconnect_chip_factory_data.py](../../scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py)
+script, add the following line to the argument list:
+
+```
+--user-data {user data JSON}
+```
+
+As `user data JSON`, provide a flat JSON map with a value file that consists of
+`string` or `int32` types. For example, you can use a JSON file that looks like
+follows:
+
+```
+{
+    "name": "product_name",
+    "version": 123,
+    "revision": "0x123"
+}
+```
+
+When added to the argument line, the final result would look like follows:
+
+```
+--user-data '{"name": "product_name", "version": 123, "revision": "0x123"}'
+```
+
+#### How to handle user data
+
+The user data is not handled anywhere in the Matter stack, so you must handle it
+in your application. To do this, you can use the
+[Factory Data Provider](../../src/platform/nrfconnect/FactoryDataProvider.h) and
+apply one of the following methods:
+
+-   `GetUserData` method to obtain raw data in the CBOR format as a
+    `MutableByteSpan`.
+
+-   `GetUserKey` method that lets you search along the user data list using a
+    specific key, and if the key exists in the user data, the method returns its
+    value.
+
+If you opt for `GetUserKey`, complete the following steps to set up the search:
+
+1. Add the `GetUserKey` method to your code.
+
+2. Given that all integer fields of the `user` Factory Data field are `int32`,
+   provide a buffer that has a size of at least `4B` or an `int32_t` variable to
+   `GetUserKey`. To read a string field from user data, the buffer should have a
+   size of at least the length of the expected string.
+
+3. Set it up to read all user data fields.
+
+Only after this setup is complete, can you use all variables in your code and
+cast the result to your own purpose.
+
+The code example of how to read all fields from the JSON example one by one can
+look like follows:
+
+    ```
+    chip::DeviceLayer::FactoryDataProvider factoryDataProvider;
+
+    factoryDataProvider.Init();
+
+    uint8_t user_name[12];
+    size_t name_len = sizeof(user_name);
+    factoryDataProvider.GetUserKey("name", user_name, name_len);
+
+    int32_t version;
+    size_t version_len = sizeof(version);
+    factoryDataProvider.GetUserKey("version", &version, version_len);
+
+    uint8_t revision[5];
+    size_t revision_len = sizeof(revision);
+    factoryDataProvider.GetUserKey("revision", revision, revision_len);
+    ```
+
 ### Verifying using the JSON Schema tool
 
 The JSON file that contains factory data can be verified using the
diff --git a/scripts/tools/nrfconnect/tests/test_generate_factory_data.py b/scripts/tools/nrfconnect/tests/test_generate_factory_data.py
index ea6ccc4..d3fd86f 100755
--- a/scripts/tools/nrfconnect/tests/test_generate_factory_data.py
+++ b/scripts/tools/nrfconnect/tests/test_generate_factory_data.py
@@ -171,6 +171,7 @@
                                    '--discriminator', '0xFED',
                                    '--rd_uid', '91a9c12a7c80700a31ddcfa7fce63e44',
                                    '--enable_key', '00112233445566778899aabbccddeeff',
+                                   '--user', '{"name": "product_name", "version": 123, "revision": "0x123"}',
                                    '-o', os.path.join(outdir, 'fd.json')
                                    ])
 
@@ -199,6 +200,15 @@
             self.assertEqual(factory_data.get('passcode'), 13243546)
             self.assertEqual(factory_data.get('rd_uid'), 'hex:91a9c12a7c80700a31ddcfa7fce63e44')
             self.assertEqual(factory_data.get('enable_key'), 'hex:00112233445566778899aabbccddeeff')
+            self.assertEqual(factory_data.get('user'), {'name': 'product_name', 'version': 123, 'revision': '0x123'})
+
+            subprocess.check_call(['python3', os.path.join(TOOLS_DIR, 'nrfconnect_generate_partition.py'),
+                                   '-i', os.path.join(outdir, 'fd.json'),
+                                   '-o', os.path.join(outdir, 'fd'),
+                                   '--offset', "0xfb000",
+                                   '--size', "0x1000",
+                                   '--raw'
+                                   ])
 
     def test_generate_spake2p_verifier_default(self):
         with tempfile.TemporaryDirectory() as outdir:
@@ -223,6 +233,7 @@
                                    '--spake2_salt', 'U1BBS0UyUCBLZXkgU2FsdA==',
                                    '--passcode', '20202021',
                                    '--discriminator', '0xFED',
+                                   '--user', '{"name": "product_name", "version": 123, "revision": "0x123"}',
                                    '-o', os.path.join(outdir, 'fd.json')
                                    ])
 
@@ -234,6 +245,15 @@
             self.assertEqual(factory_data.get('spake2_it'), 1000)
             self.assertEqual(factory_data.get('spake2_verifier'), base64_to_json(
                 'uWFwqugDNGiEck/po7KHwwMwwqZgN10XuyBajPGuyzUEV/iree4lOrao5GuwnlQ65CJzbeUB49s31EH+NEkg0JVI5MGCQGMMT/SRPFNRODm3wH/MBiehuFc6FJ/NH6Rmzw=='))
+            self.assertEqual(factory_data.get('user'), {'name': 'product_name', 'version': 123, 'revision': '0x123'})
+
+            subprocess.check_call(['python3', os.path.join(TOOLS_DIR, 'nrfconnect_generate_partition.py'),
+                                   '-i', os.path.join(outdir, 'fd.json'),
+                                   '-o', os.path.join(outdir, 'fd'),
+                                   '--offset', "0xfb000",
+                                   '--size', "0x1000",
+                                   '--raw'
+                                   ])
 
 
 if __name__ == '__main__':
diff --git a/src/platform/nrfconnect/FactoryDataParser.c b/src/platform/nrfconnect/FactoryDataParser.c
index 64f412e..1960188 100644
--- a/src/platform/nrfconnect/FactoryDataParser.c
+++ b/src/platform/nrfconnect/FactoryDataParser.c
@@ -40,6 +40,77 @@
     return false;
 }
 
+static bool DecodeEntry(zcbor_state_t * states, void * buffer, size_t bufferSize, size_t * outlen)
+{
+    struct zcbor_string tempString;
+    int32_t tempInt = 0;
+
+    // Try to decode entry as string
+    bool res = zcbor_tstr_decode(states, &tempString);
+    if (res)
+    {
+        if (bufferSize < tempString.len)
+        {
+            return false;
+        }
+        memcpy(buffer, tempString.value, tempString.len);
+        *outlen = tempString.len;
+        return res;
+    }
+
+    // Try to decode entry as int32
+    res = zcbor_int32_decode(states, &tempInt);
+    if (res)
+    {
+        if (bufferSize < sizeof(tempInt))
+        {
+            return false;
+        }
+        memcpy(buffer, &tempInt, sizeof(tempInt));
+        *outlen = sizeof(tempInt);
+        return res;
+    }
+
+    return res;
+}
+
+bool FindUserDataEntry(struct FactoryData * factoryData, const char * entry, void * buffer, size_t bufferSize, size_t * outlen)
+{
+    if ((!factoryData) || (!factoryData->user.data) || (!buffer) || (!outlen))
+    {
+        return false;
+    }
+
+    ZCBOR_STATE_D(states, MAX_FACTORY_DATA_NESTING_LEVEL - 1, factoryData->user.data, factoryData->user.len, 1);
+
+    bool res      = zcbor_map_start_decode(states);
+    bool keyFound = false;
+    struct zcbor_string currentString;
+
+    while (res)
+    {
+        res = zcbor_tstr_decode(states, &currentString);
+
+        if (!res)
+        {
+            break;
+        }
+
+        if (strncmp(entry, (const char *) currentString.value, currentString.len) == 0)
+        {
+            res      = DecodeEntry(states, buffer, bufferSize, outlen);
+            keyFound = true;
+            break;
+        }
+        else
+        {
+            res = res && zcbor_any_skip(states, NULL);
+        }
+    }
+
+    return res && keyFound && zcbor_list_map_end_force_decode(states);
+}
+
 bool ParseFactoryData(uint8_t * buffer, uint16_t bufferSize, struct FactoryData * factoryData)
 {
     memset(factoryData, 0, sizeof(*factoryData));
@@ -167,7 +238,9 @@
         }
         else if (strncmp("user", (const char *) currentString.value, currentString.len) == 0)
         {
-            res = res && zcbor_bstr_decode(states, (struct zcbor_string *) &factoryData->user);
+            factoryData->user.data = (void *) states->payload;
+            res                    = res && zcbor_any_skip(states, NULL);
+            factoryData->user.len  = (void *) states->payload - factoryData->user.data;
         }
         else
         {
diff --git a/src/platform/nrfconnect/FactoryDataParser.h b/src/platform/nrfconnect/FactoryDataParser.h
index 9c87589..b600371 100644
--- a/src/platform/nrfconnect/FactoryDataParser.h
+++ b/src/platform/nrfconnect/FactoryDataParser.h
@@ -76,6 +76,20 @@
  */
 bool ParseFactoryData(uint8_t * buffer, uint16_t bufferSize, struct FactoryData * factoryData);
 
+/**
+ * @brief Tries to find an entry within the given factory data user data field.
+ * The parser parses only the uint32 type of ints. To read int-related objects the buffer size must be aligned to uint32.
+ * That means, to obtain uint8 or uint16 value users should provide the buffer with size at least sizeof(uint32_t).
+ *
+ * @param factoryData An address of object of factory data that contains user field filled.
+ * @param entry An entry name to be find out.
+ * @param buffer Output buffer to store found key value.
+ * @param bufferSize Size of buffer. That size should have size at least equal to expected key value.
+ * @param outlen Actual size of found user data field.
+ * @return true on success, false otherwise
+ */
+bool FindUserDataEntry(struct FactoryData * factoryData, const char * entry, void * buffer, size_t bufferSize, size_t * outlen);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/platform/nrfconnect/FactoryDataProvider.cpp b/src/platform/nrfconnect/FactoryDataProvider.cpp
index ed3fd93..cd52046 100644
--- a/src/platform/nrfconnect/FactoryDataProvider.cpp
+++ b/src/platform/nrfconnect/FactoryDataProvider.cpp
@@ -339,6 +339,32 @@
     return CHIP_NO_ERROR;
 }
 
+template <class FlashFactoryData>
+CHIP_ERROR FactoryDataProvider<FlashFactoryData>::GetUserData(MutableByteSpan & userData)
+{
+    ReturnErrorCodeIf(!mFactoryData.user.data, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
+    ReturnErrorCodeIf(userData.size() < mFactoryData.user.len, CHIP_ERROR_BUFFER_TOO_SMALL);
+
+    memcpy(userData.data(), mFactoryData.user.data, mFactoryData.user.len);
+
+    userData.reduce_size(mFactoryData.user.len);
+
+    return CHIP_NO_ERROR;
+}
+
+template <class FlashFactoryData>
+CHIP_ERROR FactoryDataProvider<FlashFactoryData>::GetUserKey(const char * userKey, void * buf, size_t & len)
+{
+    ReturnErrorCodeIf(!mFactoryData.user.data, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
+    ReturnErrorCodeIf(!buf, CHIP_ERROR_BUFFER_TOO_SMALL);
+
+    bool success = FindUserDataEntry(&mFactoryData, userKey, buf, len, &len);
+
+    ReturnErrorCodeIf(!success, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
+
+    return CHIP_NO_ERROR;
+}
+
 // Fully instantiate the template class in whatever compilation unit includes this file.
 template class FactoryDataProvider<InternalFlashFactoryData>;
 template class FactoryDataProvider<ExternalFlashFactoryData>;
diff --git a/src/platform/nrfconnect/FactoryDataProvider.h b/src/platform/nrfconnect/FactoryDataProvider.h
index a7111ac..3d46076 100644
--- a/src/platform/nrfconnect/FactoryDataProvider.h
+++ b/src/platform/nrfconnect/FactoryDataProvider.h
@@ -111,6 +111,28 @@
     // ===== Members functions that are platform-specific
     CHIP_ERROR GetEnableKey(MutableByteSpan & enableKey);
 
+    /**
+     * @brief Get the user data in CBOR format as MutableByteSpan
+     *
+     * @param userData MutableByteSpan object to obtain all user data in CBOR format
+     * @returns
+     * CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND if factory data does not contain user field, or the value cannot be read out.
+     * CHIP_ERROR_BUFFER_TOO_SMALL if provided MutableByteSpan is too small
+     */
+    CHIP_ERROR GetUserData(MutableByteSpan & userData);
+
+    /**
+     * @brief Try to find user data key and return its value
+     *
+     * @param userKey A key name to be found
+     * @param buf Buffer to store value of found key
+     * @param len Length of the buffer. This value will be updated to the actual value if the key is read.
+     * @returns
+     * CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND if factory data does not contain user field, or the value cannot be read out.
+     * CHIP_ERROR_BUFFER_TOO_SMALL if provided buffer length is too small
+     */
+    CHIP_ERROR GetUserKey(const char * userKey, void * buf, size_t & len);
+
 private:
     static constexpr uint16_t kFactoryDataPartitionSize    = PM_FACTORY_DATA_SIZE;
     static constexpr uint32_t kFactoryDataPartitionAddress = PM_FACTORY_DATA_ADDRESS;