1 /*
2  * Copyright (c) 2023 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/ztest.h>
8 #include <zephyr/storage/flash_map.h>
9 #include <zephyr/bluetooth/mesh.h>
10 
11 #include "mesh/blob.h"
12 
13 #define SLOT1_PARTITION		slot1_partition
14 #define SLOT1_PARTITION_ID	FIXED_PARTITION_ID(SLOT1_PARTITION)
15 #define SLOT1_PARTITION_SIZE	FIXED_PARTITION_SIZE(SLOT1_PARTITION)
16 /* Chunk size is set to value that is not multiple of 4, to verify that chunks are written correctly
17  * even if they are not aligned with word length used in flash
18  */
19 #define CHUNK_SIZE		65
20 
21 static struct bt_mesh_blob_io_flash blob_flash_stream;
22 
chunk_size(const struct bt_mesh_blob_block * block,uint16_t chunk_idx)23 static size_t chunk_size(const struct bt_mesh_blob_block *block,
24 				uint16_t chunk_idx)
25 {
26 	if ((chunk_idx == block->chunk_count - 1) &&
27 	    (block->size % CHUNK_SIZE)) {
28 		return block->size % CHUNK_SIZE;
29 	}
30 
31 	return CHUNK_SIZE;
32 }
33 
block_size_to_log(size_t size)34 static uint8_t block_size_to_log(size_t size)
35 {
36 	uint8_t block_size_log = 0;
37 
38 	while (size > 1) {
39 		size = size / 2;
40 		block_size_log++;
41 	}
42 
43 	return block_size_log;
44 }
45 
46 ZTEST_SUITE(blob_io_flash, NULL, NULL, NULL, NULL, NULL);
47 
ZTEST(blob_io_flash,test_chunk_read)48 ZTEST(blob_io_flash, test_chunk_read)
49 {
50 	const struct flash_area *fa = NULL;
51 	struct bt_mesh_blob_xfer xfer;
52 	struct bt_mesh_blob_block block = { 0 };
53 	struct bt_mesh_blob_chunk chunk = { 0 };
54 	size_t remaining = SLOT1_PARTITION_SIZE;
55 	off_t block_idx = 0;
56 	uint16_t chunk_idx = 0;
57 	uint8_t chunk_data[CHUNK_SIZE];
58 	uint8_t test_data[SLOT1_PARTITION_SIZE];
59 	uint8_t ctrl_data[SLOT1_PARTITION_SIZE];
60 	size_t tests_data_offset = 0;
61 	int i, err;
62 
63 	/* Fill test data with pattern */
64 	for (i = 0; i < SLOT1_PARTITION_SIZE; i++) {
65 		test_data[i] = i % 0xFF;
66 	}
67 
68 	err = flash_area_open(SLOT1_PARTITION_ID, &fa);
69 	zassert_equal(err, 0, "Preparing test data failed with err=%d", err);
70 
71 	err = flash_area_flatten(fa, 0, ARRAY_SIZE(ctrl_data));
72 	zassert_equal(err, 0, "Preparing test data failed with err=%d", err);
73 
74 	err = flash_area_write(fa, 0, test_data, ARRAY_SIZE(ctrl_data));
75 	zassert_equal(err, 0, "Preparing test data failed with err=%d", err);
76 
77 	err = flash_area_read(fa, 0, ctrl_data, ARRAY_SIZE(ctrl_data));
78 	zassert_equal(err, 0, "Preparing test data failed with err=%d", err);
79 
80 	zassert_mem_equal(ctrl_data, test_data, ARRAY_SIZE(ctrl_data),
81 			  "Incorrect data written into flash");
82 
83 	memset(ctrl_data, 0, SLOT1_PARTITION_SIZE);
84 
85 	flash_area_close(fa);
86 
87 	err = bt_mesh_blob_io_flash_init(&blob_flash_stream,
88 					 SLOT1_PARTITION_ID, 0);
89 	zassert_equal(err, 0, "BLOB I/O init failed with err=%d", err);
90 
91 	err = blob_flash_stream.io.open(&blob_flash_stream.io, &xfer, BT_MESH_BLOB_READ);
92 	zassert_equal(err, 0, "BLOB I/O open failed with err=%d", err);
93 
94 	chunk.data = chunk_data;
95 
96 	/* Simulate reading whole partition divided into blocks and chunk of maximum sizes */
97 	while (remaining > 0) {
98 		block.chunk_count =
99 			DIV_ROUND_UP(CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX,
100 					 CHUNK_SIZE);
101 		block.size = remaining > CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX
102 				     ? CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX
103 				     : remaining;
104 
105 		/* BLOB Client should do nothing to flash area as it's in read mode */
106 		err = blob_flash_stream.io.block_start(&blob_flash_stream.io, &xfer, &block);
107 		zassert_equal(err, 0, "BLOB I/O open failed with err=%d", err);
108 
109 		/* `block_start` in write mode will erase flash pages that can fit block.
110 		 * Assert that at least block size of data was not erased in read mode
111 		 */
112 		flash_area_read(blob_flash_stream.area, block.offset, ctrl_data, block.size);
113 		zassert_mem_equal(ctrl_data, &test_data[block.offset], block.size,
114 				  "Flash data was altered by `block_start` in read mode");
115 
116 		memset(ctrl_data, 0, SLOT1_PARTITION_SIZE);
117 
118 		block.offset = block_idx * (1 << block_size_to_log(block.size));
119 
120 		for (i = 0; i < block.chunk_count; i++) {
121 			chunk.size = chunk_size(&block, chunk_idx);
122 			chunk.offset = CHUNK_SIZE * chunk_idx;
123 
124 			err = blob_flash_stream.io.rd(&blob_flash_stream.io, &xfer, &block, &chunk);
125 			zassert_equal(err, 0, "BLOB I/O read failed with err=%d off=%d len=%d",
126 				      err, block.offset + chunk.offset, chunk.size);
127 
128 			zassert_mem_equal(&chunk_data, &test_data[tests_data_offset], chunk.size,
129 					  "Incorrect data written into flash");
130 			chunk_idx++;
131 
132 			remaining -= chunk.size;
133 			tests_data_offset += chunk.size;
134 		}
135 		block_idx++;
136 		chunk_idx = 0;
137 	}
138 
139 	/* We read whole sector as BLOB. Try to increment every offset by one and read,
140 	 * which should attempt to read outside flash area
141 	 */
142 	chunk.offset++;
143 	err = blob_flash_stream.io.rd(&blob_flash_stream.io, &xfer, &block, &chunk);
144 	zassert_false(err == 0, "Read outside flash area successful");
145 
146 	chunk.offset--;
147 	block.offset++;
148 	err = blob_flash_stream.io.rd(&blob_flash_stream.io, &xfer, &block, &chunk);
149 	zassert_false(err == 0, "Read outside flash area successful");
150 
151 	block.offset--;
152 	blob_flash_stream.offset++;
153 	err = blob_flash_stream.io.rd(&blob_flash_stream.io, &xfer, &block, &chunk);
154 	zassert_false(err == 0, "Read outside flash area successful");
155 
156 	blob_flash_stream.io.close(&blob_flash_stream.io, &xfer);
157 }
158 
ZTEST(blob_io_flash,test_chunk_write)159 ZTEST(blob_io_flash, test_chunk_write)
160 {
161 	struct bt_mesh_blob_xfer xfer;
162 	struct bt_mesh_blob_block block = { 0 };
163 	struct bt_mesh_blob_chunk chunk = { 0 };
164 	size_t remaining = SLOT1_PARTITION_SIZE;
165 	off_t block_idx = 0;
166 	uint16_t chunk_idx = 0;
167 	uint8_t chunk_data[CHUNK_SIZE];
168 	/* 3 is maximum length of padding at the end of written chunk */
169 	uint8_t chunk_ctrl_data[CHUNK_SIZE + 3];
170 	uint8_t end_padding_len;
171 	uint8_t test_data[SLOT1_PARTITION_SIZE];
172 	uint8_t erased_block_data[CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX];
173 	uint8_t ctrl_data[SLOT1_PARTITION_SIZE];
174 	size_t tests_data_offset = 0;
175 	int i, j, err;
176 
177 	/* Fill test data with pattern */
178 	for (i = 0; i < SLOT1_PARTITION_SIZE; i++) {
179 		test_data[i] = i % 0xFF;
180 	}
181 
182 	err = bt_mesh_blob_io_flash_init(&blob_flash_stream,
183 					 SLOT1_PARTITION_ID, 0);
184 	zassert_equal(err, 0, "BLOB I/O init failed with err=%d", err);
185 
186 	err = blob_flash_stream.io.open(&blob_flash_stream.io, &xfer, BT_MESH_BLOB_WRITE);
187 	zassert_equal(err, 0, "BLOB I/O open failed with err=%d", err);
188 
189 	chunk.data = chunk_data;
190 
191 	memset(erased_block_data, flash_area_erased_val(blob_flash_stream.area),
192 	       CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX);
193 	/* Simulate writing whole partition divided into blocks and chunk of maximum sizes */
194 	while (remaining > 0) {
195 		block.chunk_count =
196 			DIV_ROUND_UP(CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX,
197 					 CHUNK_SIZE);
198 		block.size = remaining > CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX
199 				     ? CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX
200 				     : remaining;
201 		block.offset = block_idx * (1 << block_size_to_log(block.size));
202 
203 		err = blob_flash_stream.io.block_start(&blob_flash_stream.io, &xfer, &block);
204 		zassert_equal(err, 0, "BLOB I/O open failed with err=%d", err);
205 
206 		flash_area_read(blob_flash_stream.area, block.offset,
207 				ctrl_data, block.size);
208 
209 		zassert_mem_equal(ctrl_data, erased_block_data, block.size,
210 				  "Flash data was not erased by `block_start` in write mode");
211 
212 		memset(ctrl_data, 0, SLOT1_PARTITION_SIZE);
213 
214 		for (i = 0; i < block.chunk_count; i++) {
215 			chunk.size = chunk_size(&block, chunk_idx);
216 			chunk.offset = CHUNK_SIZE * chunk_idx;
217 
218 			memcpy(chunk.data,
219 			       &test_data[chunk.offset + block.offset],
220 			       chunk.size);
221 
222 			err = blob_flash_stream.io.wr(&blob_flash_stream.io, &xfer, &block, &chunk);
223 			zassert_equal(err, 0, "BLOB I/O write failed with err=%d", err);
224 
225 			/* To calculate end padding length we must calculate size of whole buffer
226 			 * and subtract start offset length and chunk size
227 			 */
228 			end_padding_len =
229 				ROUND_UP((block.offset + chunk.offset) %
230 					 flash_area_align(blob_flash_stream.area) +
231 					 chunk.size, flash_area_align(blob_flash_stream.area)) -
232 				(block.offset + chunk.offset) %
233 				flash_area_align(blob_flash_stream.area) - chunk.size;
234 
235 			flash_area_read(blob_flash_stream.area, block.offset + chunk.offset,
236 					chunk_ctrl_data, chunk.size + end_padding_len);
237 
238 			zassert_mem_equal(chunk_ctrl_data, chunk_data, chunk.size,
239 					  "Incorrect data written into flash");
240 
241 			/* Assert that nothing was written into end padding */
242 			for (j = 1; j <= end_padding_len; j++) {
243 				zassert_equal(chunk_ctrl_data[chunk.size + j],
244 					      flash_area_erased_val(blob_flash_stream.area));
245 			}
246 			chunk_idx++;
247 
248 			remaining -= chunk.size;
249 			tests_data_offset += chunk.size;
250 		}
251 
252 		block_idx++;
253 		chunk_idx = 0;
254 	}
255 
256 	flash_area_read(blob_flash_stream.area, 0, ctrl_data, SLOT1_PARTITION_SIZE);
257 	zassert_mem_equal(ctrl_data, test_data, SLOT1_PARTITION_SIZE,
258 			  "Incorrect chunks written into flash");
259 
260 	/* We wrote whole sector as BLOB. Try to increment every offset by one and write,
261 	 * which should attempt to write outside flash area
262 	 */
263 	chunk.offset++;
264 	err = blob_flash_stream.io.wr(&blob_flash_stream.io, &xfer, &block, &chunk);
265 	zassert_false(err == 0, "Write outside flash area successful");
266 
267 	chunk.offset--;
268 	block.offset++;
269 	err = blob_flash_stream.io.wr(&blob_flash_stream.io, &xfer, &block, &chunk);
270 	zassert_false(err == 0, "Write outside flash area successful");
271 
272 	block.offset--;
273 	blob_flash_stream.offset++;
274 	err = blob_flash_stream.io.wr(&blob_flash_stream.io, &xfer, &block, &chunk);
275 	zassert_false(err == 0, "Write outside flash area successful");
276 
277 	blob_flash_stream.io.close(&blob_flash_stream.io, &xfer);
278 }
279