Skip to content

Commit 2521576

Browse files
ArekBalysNordicrestyled-commits
authored andcommitted
[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>
1 parent 51c5ead commit 2521576

File tree

6 files changed

+242
-2
lines changed

6 files changed

+242
-2
lines changed

docs/guides/nrfconnect_factory_data_configuration.md

+86-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ data secure by applying hardware write protection.
4141
- [Enabling factory data support](#enabling-factory-data-support)
4242
- [Generating factory data](#generating-factory-data)
4343
- [Creating factory data JSON file with the first script](#creating-factory-data-json-file-with-the-first-script)
44+
- [How to set user data](#how-to-set-user-data)
45+
- [How to handle user data](#how-to-handle-user-data)
4446
- [Verifying using the JSON Schema tool](#verifying-using-the-json-schema-tool)
4547
- [Option 1: Using the php-json-schema tool](#option-1-using-the-php-json-schema-tool)
4648
- [Option 2: Using a website validator](#option-2-using-a-website-validator)
@@ -110,7 +112,7 @@ The following table lists the parameters of a factory data set:
110112
| `spake2_verifier` | SPAKE2+ verifier | 97 B | byte string | mandatory | The SPAKE2+ verifier generated using SPAKE2+ salt, iteration counter, and passcode. |
111113
| `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. |
112114
| `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`. |
113-
| `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. |
115+
| `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. |
114116

115117
### Factory data format
116118

@@ -345,6 +347,89 @@ If the script finishes successfully, go to the location you provided with the
345347
> location as an existing file. To allow overwriting, add the `--overwrite`
346348
> option to the argument list of the Python script.
347349
350+
### How to set user data
351+
352+
The user data is an optional field provided in the factory data JSON file and
353+
depends on the manufacturer's purpose. The `user` field in a JSON factory data
354+
file is represented by a flat JSON map and it can consist of `string` or `int32`
355+
data types only. On the device side, the `user` data will be available as a CBOR
356+
map containing all defined `string` and `int32` fields.
357+
358+
To add user data as an argument to the
359+
[generate_nrfconnect_chip_factory_data.py](../../scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py)
360+
script, add the following line to the argument list:
361+
362+
```
363+
--user-data {user data JSON}
364+
```
365+
366+
As `user data JSON`, provide a flat JSON map with a value file that consists of
367+
`string` or `int32` types. For example, you can use a JSON file that looks like
368+
follows:
369+
370+
```
371+
{
372+
"name": "product_name",
373+
"version": 123,
374+
"revision": "0x123"
375+
}
376+
```
377+
378+
When added to the argument line, the final result would look like follows:
379+
380+
```
381+
--user-data '{"name": "product_name", "version": 123, "revision": "0x123"}'
382+
```
383+
384+
#### How to handle user data
385+
386+
The user data is not handled anywhere in the Matter stack, so you must handle it
387+
in your application. To do this, you can use the
388+
[Factory Data Provider](../../src/platform/nrfconnect/FactoryDataProvider.h) and
389+
apply one of the following methods:
390+
391+
- `GetUserData` method to obtain raw data in the CBOR format as a
392+
`MutableByteSpan`.
393+
394+
- `GetUserKey` method that lets you search along the user data list using a
395+
specific key, and if the key exists in the user data, the method returns its
396+
value.
397+
398+
If you opt for `GetUserKey`, complete the following steps to set up the search:
399+
400+
1. Add the `GetUserKey` method to your code.
401+
402+
2. Given that all integer fields of the `user` Factory Data field are `int32`,
403+
provide a buffer that has a size of at least `4B` or an `int32_t` variable to
404+
`GetUserKey`. To read a string field from user data, the buffer should have a
405+
size of at least the length of the expected string.
406+
407+
3. Set it up to read all user data fields.
408+
409+
Only after this setup is complete, can you use all variables in your code and
410+
cast the result to your own purpose.
411+
412+
The code example of how to read all fields from the JSON example one by one can
413+
look like follows:
414+
415+
```
416+
chip::DeviceLayer::FactoryDataProvider factoryDataProvider;
417+
418+
factoryDataProvider.Init();
419+
420+
uint8_t user_name[12];
421+
size_t name_len = sizeof(user_name);
422+
factoryDataProvider.GetUserKey("name", user_name, name_len);
423+
424+
int32_t version;
425+
size_t version_len = sizeof(version);
426+
factoryDataProvider.GetUserKey("version", &version, version_len);
427+
428+
uint8_t revision[5];
429+
size_t revision_len = sizeof(revision);
430+
factoryDataProvider.GetUserKey("revision", revision, revision_len);
431+
```
432+
348433
### Verifying using the JSON Schema tool
349434
350435
The JSON file that contains factory data can be verified using the

scripts/tools/nrfconnect/tests/test_generate_factory_data.py

+20
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ def test_generate_factory_data_all_specified(self):
171171
'--discriminator', '0xFED',
172172
'--rd_uid', '91a9c12a7c80700a31ddcfa7fce63e44',
173173
'--enable_key', '00112233445566778899aabbccddeeff',
174+
'--user', '{"name": "product_name", "version": 123, "revision": "0x123"}',
174175
'-o', os.path.join(outdir, 'fd.json')
175176
])
176177

@@ -199,6 +200,15 @@ def test_generate_factory_data_all_specified(self):
199200
self.assertEqual(factory_data.get('passcode'), 13243546)
200201
self.assertEqual(factory_data.get('rd_uid'), 'hex:91a9c12a7c80700a31ddcfa7fce63e44')
201202
self.assertEqual(factory_data.get('enable_key'), 'hex:00112233445566778899aabbccddeeff')
203+
self.assertEqual(factory_data.get('user'), {'name': 'product_name', 'version': 123, 'revision': '0x123'})
204+
205+
subprocess.check_call(['python3', os.path.join(TOOLS_DIR, 'nrfconnect_generate_partition.py'),
206+
'-i', os.path.join(outdir, 'fd.json'),
207+
'-o', os.path.join(outdir, 'fd'),
208+
'--offset', "0xfb000",
209+
'--size', "0x1000",
210+
'--raw'
211+
])
202212

203213
def test_generate_spake2p_verifier_default(self):
204214
with tempfile.TemporaryDirectory() as outdir:
@@ -223,6 +233,7 @@ def test_generate_spake2p_verifier_default(self):
223233
'--spake2_salt', 'U1BBS0UyUCBLZXkgU2FsdA==',
224234
'--passcode', '20202021',
225235
'--discriminator', '0xFED',
236+
'--user', '{"name": "product_name", "version": 123, "revision": "0x123"}',
226237
'-o', os.path.join(outdir, 'fd.json')
227238
])
228239

@@ -234,6 +245,15 @@ def test_generate_spake2p_verifier_default(self):
234245
self.assertEqual(factory_data.get('spake2_it'), 1000)
235246
self.assertEqual(factory_data.get('spake2_verifier'), base64_to_json(
236247
'uWFwqugDNGiEck/po7KHwwMwwqZgN10XuyBajPGuyzUEV/iree4lOrao5GuwnlQ65CJzbeUB49s31EH+NEkg0JVI5MGCQGMMT/SRPFNRODm3wH/MBiehuFc6FJ/NH6Rmzw=='))
248+
self.assertEqual(factory_data.get('user'), {'name': 'product_name', 'version': 123, 'revision': '0x123'})
249+
250+
subprocess.check_call(['python3', os.path.join(TOOLS_DIR, 'nrfconnect_generate_partition.py'),
251+
'-i', os.path.join(outdir, 'fd.json'),
252+
'-o', os.path.join(outdir, 'fd'),
253+
'--offset', "0xfb000",
254+
'--size', "0x1000",
255+
'--raw'
256+
])
237257

238258

239259
if __name__ == '__main__':

src/platform/nrfconnect/FactoryDataParser.c

+74-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,77 @@ static inline bool uint16_decode(zcbor_state_t * states, uint16_t * value)
4040
return false;
4141
}
4242

43+
static bool DecodeEntry(zcbor_state_t * states, void * buffer, size_t bufferSize, size_t * outlen)
44+
{
45+
struct zcbor_string tempString;
46+
int32_t tempInt = 0;
47+
48+
// Try to decode entry as string
49+
bool res = zcbor_tstr_decode(states, &tempString);
50+
if (res)
51+
{
52+
if (bufferSize < tempString.len)
53+
{
54+
return false;
55+
}
56+
memcpy(buffer, tempString.value, tempString.len);
57+
*outlen = tempString.len;
58+
return res;
59+
}
60+
61+
// Try to decode entry as int32
62+
res = zcbor_int32_decode(states, &tempInt);
63+
if (res)
64+
{
65+
if (bufferSize < sizeof(tempInt))
66+
{
67+
return false;
68+
}
69+
memcpy(buffer, &tempInt, sizeof(tempInt));
70+
*outlen = sizeof(tempInt);
71+
return res;
72+
}
73+
74+
return res;
75+
}
76+
77+
bool FindUserDataEntry(struct FactoryData * factoryData, const char * entry, void * buffer, size_t bufferSize, size_t * outlen)
78+
{
79+
if ((!factoryData) || (!factoryData->user.data) || (!buffer) || (!outlen))
80+
{
81+
return false;
82+
}
83+
84+
ZCBOR_STATE_D(states, MAX_FACTORY_DATA_NESTING_LEVEL - 1, factoryData->user.data, factoryData->user.len, 1);
85+
86+
bool res = zcbor_map_start_decode(states);
87+
bool keyFound = false;
88+
struct zcbor_string currentString;
89+
90+
while (res)
91+
{
92+
res = zcbor_tstr_decode(states, &currentString);
93+
94+
if (!res)
95+
{
96+
break;
97+
}
98+
99+
if (strncmp(entry, (const char *) currentString.value, currentString.len) == 0)
100+
{
101+
res = DecodeEntry(states, buffer, bufferSize, outlen);
102+
keyFound = true;
103+
break;
104+
}
105+
else
106+
{
107+
res = res && zcbor_any_skip(states, NULL);
108+
}
109+
}
110+
111+
return res && keyFound && zcbor_list_map_end_force_decode(states);
112+
}
113+
43114
bool ParseFactoryData(uint8_t * buffer, uint16_t bufferSize, struct FactoryData * factoryData)
44115
{
45116
memset(factoryData, 0, sizeof(*factoryData));
@@ -167,7 +238,9 @@ bool ParseFactoryData(uint8_t * buffer, uint16_t bufferSize, struct FactoryData
167238
}
168239
else if (strncmp("user", (const char *) currentString.value, currentString.len) == 0)
169240
{
170-
res = res && zcbor_bstr_decode(states, (struct zcbor_string *) &factoryData->user);
241+
factoryData->user.data = (void *) states->payload;
242+
res = res && zcbor_any_skip(states, NULL);
243+
factoryData->user.len = (void *) states->payload - factoryData->user.data;
171244
}
172245
else
173246
{

src/platform/nrfconnect/FactoryDataParser.h

+14
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,20 @@ struct FactoryData
7676
*/
7777
bool ParseFactoryData(uint8_t * buffer, uint16_t bufferSize, struct FactoryData * factoryData);
7878

79+
/**
80+
* @brief Tries to find an entry within the given factory data user data field.
81+
* The parser parses only the uint32 type of ints. To read int-related objects the buffer size must be aligned to uint32.
82+
* That means, to obtain uint8 or uint16 value users should provide the buffer with size at least sizeof(uint32_t).
83+
*
84+
* @param factoryData An address of object of factory data that contains user field filled.
85+
* @param entry An entry name to be find out.
86+
* @param buffer Output buffer to store found key value.
87+
* @param bufferSize Size of buffer. That size should have size at least equal to expected key value.
88+
* @param outlen Actual size of found user data field.
89+
* @return true on success, false otherwise
90+
*/
91+
bool FindUserDataEntry(struct FactoryData * factoryData, const char * entry, void * buffer, size_t bufferSize, size_t * outlen);
92+
7993
#ifdef __cplusplus
8094
}
8195
#endif

src/platform/nrfconnect/FactoryDataProvider.cpp

+26
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,32 @@ CHIP_ERROR FactoryDataProvider<FlashFactoryData>::GetEnableKey(MutableByteSpan &
339339
return CHIP_NO_ERROR;
340340
}
341341

342+
template <class FlashFactoryData>
343+
CHIP_ERROR FactoryDataProvider<FlashFactoryData>::GetUserData(MutableByteSpan & userData)
344+
{
345+
ReturnErrorCodeIf(!mFactoryData.user.data, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
346+
ReturnErrorCodeIf(userData.size() < mFactoryData.user.len, CHIP_ERROR_BUFFER_TOO_SMALL);
347+
348+
memcpy(userData.data(), mFactoryData.user.data, mFactoryData.user.len);
349+
350+
userData.reduce_size(mFactoryData.user.len);
351+
352+
return CHIP_NO_ERROR;
353+
}
354+
355+
template <class FlashFactoryData>
356+
CHIP_ERROR FactoryDataProvider<FlashFactoryData>::GetUserKey(const char * userKey, void * buf, size_t & len)
357+
{
358+
ReturnErrorCodeIf(!mFactoryData.user.data, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
359+
ReturnErrorCodeIf(!buf, CHIP_ERROR_BUFFER_TOO_SMALL);
360+
361+
bool success = FindUserDataEntry(&mFactoryData, userKey, buf, len, &len);
362+
363+
ReturnErrorCodeIf(!success, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
364+
365+
return CHIP_NO_ERROR;
366+
}
367+
342368
// Fully instantiate the template class in whatever compilation unit includes this file.
343369
template class FactoryDataProvider<InternalFlashFactoryData>;
344370
template class FactoryDataProvider<ExternalFlashFactoryData>;

src/platform/nrfconnect/FactoryDataProvider.h

+22
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,28 @@ class FactoryDataProvider : public chip::Credentials::DeviceAttestationCredentia
111111
// ===== Members functions that are platform-specific
112112
CHIP_ERROR GetEnableKey(MutableByteSpan & enableKey);
113113

114+
/**
115+
* @brief Get the user data in CBOR format as MutableByteSpan
116+
*
117+
* @param userData MutableByteSpan object to obtain all user data in CBOR format
118+
* @returns
119+
* CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND if factory data does not contain user field, or the value cannot be read out.
120+
* CHIP_ERROR_BUFFER_TOO_SMALL if provided MutableByteSpan is too small
121+
*/
122+
CHIP_ERROR GetUserData(MutableByteSpan & userData);
123+
124+
/**
125+
* @brief Try to find user data key and return its value
126+
*
127+
* @param userKey A key name to be found
128+
* @param buf Buffer to store value of found key
129+
* @param len Length of the buffer. This value will be updated to the actual value if the key is read.
130+
* @returns
131+
* CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND if factory data does not contain user field, or the value cannot be read out.
132+
* CHIP_ERROR_BUFFER_TOO_SMALL if provided buffer length is too small
133+
*/
134+
CHIP_ERROR GetUserKey(const char * userKey, void * buf, size_t & len);
135+
114136
private:
115137
static constexpr uint16_t kFactoryDataPartitionSize = PM_FACTORY_DATA_SIZE;
116138
static constexpr uint32_t kFactoryDataPartitionAddress = PM_FACTORY_DATA_ADDRESS;

0 commit comments

Comments
 (0)