]> granicus.if.org Git - esp-idf/blob - components/app_update/esp_ota_ops.c
heap: test: don’t warn about oversized mallocs
[esp-idf] / components / app_update / esp_ota_ops.c
1 // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <stdint.h>
16 #include <stdbool.h>
17 #include <stddef.h>
18 #include <stdlib.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <assert.h>
22 #include <freertos/FreeRTOS.h>
23 #include <freertos/task.h>
24
25 #include "esp_err.h"
26 #include "esp_partition.h"
27 #include "esp_spi_flash.h"
28 #include "esp_image_format.h"
29 #include "esp_secure_boot.h"
30 #include "esp_flash_encrypt.h"
31 #include "sdkconfig.h"
32
33 #include "esp_ota_ops.h"
34 #include "rom/queue.h"
35 #include "rom/crc.h"
36 #include "soc/dport_reg.h"
37 #include "esp_log.h"
38
39
40 #define OTA_MAX(a,b) ((a) >= (b) ? (a) : (b)) 
41 #define OTA_MIN(a,b) ((a) <= (b) ? (a) : (b)) 
42 #define SUB_TYPE_ID(i) (i & 0x0F) 
43
44 typedef struct ota_ops_entry_ {
45     uint32_t handle;
46     const esp_partition_t *part;
47     uint32_t erased_size;
48     uint32_t wrote_size;
49     uint8_t partial_bytes;
50     uint8_t partial_data[16];
51     LIST_ENTRY(ota_ops_entry_) entries;
52 } ota_ops_entry_t;
53
54 /* OTA selection structure (two copies in the OTA data partition.)
55    Size of 32 bytes is friendly to flash encryption */
56 typedef struct {
57     uint32_t ota_seq;
58     uint8_t  seq_label[24];
59     uint32_t crc;                /* CRC32 of ota_seq field only */
60 } ota_select;
61
62 static LIST_HEAD(ota_ops_entries_head, ota_ops_entry_) s_ota_ops_entries_head =
63     LIST_HEAD_INITIALIZER(s_ota_ops_entries_head);
64
65 static uint32_t s_ota_ops_last_handle = 0;
66 static ota_select s_ota_select[2];
67
68 const static char *TAG = "esp_ota_ops";
69
70 /* Return true if this is an OTA app partition */
71 static bool is_ota_partition(const esp_partition_t *p)
72 {
73     return (p != NULL
74             && p->type == ESP_PARTITION_TYPE_APP
75             && p->subtype >= ESP_PARTITION_SUBTYPE_APP_OTA_0
76             && p->subtype < ESP_PARTITION_SUBTYPE_APP_OTA_MAX);
77 }
78
79 esp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, esp_ota_handle_t *out_handle)
80 {
81     ota_ops_entry_t *new_entry;
82     esp_err_t ret = ESP_OK;
83
84     if ((partition == NULL) || (out_handle == NULL)) {
85         return ESP_ERR_INVALID_ARG;
86     }
87
88     partition = esp_partition_verify(partition);
89     if (partition == NULL) {
90         return ESP_ERR_NOT_FOUND;
91     }
92
93     if (!is_ota_partition(partition)) {
94         return ESP_ERR_INVALID_ARG;
95     }
96
97     if (partition == esp_ota_get_running_partition()) {
98         return ESP_ERR_OTA_PARTITION_CONFLICT;
99     }
100
101     // If input image size is 0 or OTA_SIZE_UNKNOWN, erase entire partition
102     if ((image_size == 0) || (image_size == OTA_SIZE_UNKNOWN)) {
103         ret = esp_partition_erase_range(partition, 0, partition->size);
104     } else {
105         ret = esp_partition_erase_range(partition, 0, (image_size / SPI_FLASH_SEC_SIZE + 1) * SPI_FLASH_SEC_SIZE);
106     }
107
108     if (ret != ESP_OK) {
109         return ret;
110     }
111
112     new_entry = (ota_ops_entry_t *) calloc(sizeof(ota_ops_entry_t), 1);
113     if (new_entry == NULL) {
114         return ESP_ERR_NO_MEM;
115     }
116
117     LIST_INSERT_HEAD(&s_ota_ops_entries_head, new_entry, entries);
118
119     if ((image_size == 0) || (image_size == OTA_SIZE_UNKNOWN)) {
120         new_entry->erased_size = partition->size;
121     } else {
122         new_entry->erased_size = image_size;
123     }
124
125     new_entry->part = partition;
126     new_entry->handle = ++s_ota_ops_last_handle;
127     *out_handle = new_entry->handle;
128     return ESP_OK;
129 }
130
131 esp_err_t esp_ota_write(esp_ota_handle_t handle, const void *data, size_t size)
132 {
133     const uint8_t *data_bytes = (const uint8_t *)data;
134     esp_err_t ret;
135     ota_ops_entry_t *it;
136
137     if (data == NULL) {
138         ESP_LOGE(TAG, "write data is invalid");
139         return ESP_ERR_INVALID_ARG;
140     }
141
142     // find ota handle in linked list
143     for (it = LIST_FIRST(&s_ota_ops_entries_head); it != NULL; it = LIST_NEXT(it, entries)) {
144         if (it->handle == handle) {
145             // must erase the partition before writing to it
146             assert(it->erased_size > 0 && "must erase the partition before writing to it");
147
148             if(it->wrote_size == 0 && size > 0 && data_bytes[0] != 0xE9) {
149                 ESP_LOGE(TAG, "OTA image has invalid magic byte (expected 0xE9, saw 0x%02x", data_bytes[0]);
150                 return ESP_ERR_OTA_VALIDATE_FAILED;
151             }
152
153             if (esp_flash_encryption_enabled()) {
154                 /* Can only write 16 byte blocks to flash, so need to cache anything else */
155                 size_t copy_len;
156
157                 /* check if we have partially written data from earlier */
158                 if (it->partial_bytes != 0) {
159                     copy_len = OTA_MIN(16 - it->partial_bytes, size);
160                     memcpy(it->partial_data + it->partial_bytes, data_bytes, copy_len);
161                     it->partial_bytes += copy_len;
162                     if (it->partial_bytes != 16) {
163                         return ESP_OK; /* nothing to write yet, just filling buffer */
164                     }
165                     /* write 16 byte to partition */
166                     ret = esp_partition_write(it->part, it->wrote_size, it->partial_data, 16);
167                     if (ret != ESP_OK) {
168                         return ret;
169                     }
170                     it->partial_bytes = 0;
171                     memset(it->partial_data, 0xFF, 16);
172                     it->wrote_size += 16;
173                     data_bytes += copy_len;
174                     size -= copy_len;
175                 }
176
177                 /* check if we need to save trailing data that we're about to write */
178                 it->partial_bytes = size % 16;
179                 if (it->partial_bytes != 0) {
180                     size -= it->partial_bytes;
181                     memcpy(it->partial_data, data_bytes + size, it->partial_bytes);
182                 }
183             }
184
185             ret = esp_partition_write(it->part, it->wrote_size, data_bytes, size);
186             if(ret == ESP_OK){
187                 it->wrote_size += size;
188             }
189             return ret;
190         }
191     }
192
193     //if go to here ,means don't find the handle
194     ESP_LOGE(TAG,"not found the handle");
195     return ESP_ERR_INVALID_ARG;
196 }
197
198 esp_err_t esp_ota_end(esp_ota_handle_t handle)
199 {
200     ota_ops_entry_t *it;
201     esp_err_t ret = ESP_OK;
202
203     for (it = LIST_FIRST(&s_ota_ops_entries_head); it != NULL; it = LIST_NEXT(it, entries)) {
204         if (it->handle == handle) {
205             break;
206         }
207     }
208
209     if (it == NULL) {
210         return ESP_ERR_NOT_FOUND;
211     }
212
213     /* 'it' holds the ota_ops_entry_t for 'handle' */
214
215     // esp_ota_end() is only valid if some data was written to this handle
216     if ((it->erased_size == 0) || (it->wrote_size == 0)) {
217         ret = ESP_ERR_INVALID_ARG;
218         goto cleanup;
219     }
220
221     if (it->partial_bytes > 0) {
222         /* Write out last 16 bytes, if necessary */
223         ret = esp_partition_write(it->part, it->wrote_size, it->partial_data, 16);
224         if (ret != ESP_OK) {
225             ret = ESP_ERR_INVALID_STATE;
226             goto cleanup;
227         }
228         it->wrote_size += 16;
229         it->partial_bytes = 0;
230     }
231
232     esp_image_metadata_t data;
233     const esp_partition_pos_t part_pos = {
234       .offset = it->part->address,
235       .size = it->part->size,
236     };
237
238     if (esp_image_verify(ESP_IMAGE_VERIFY, &part_pos, &data) != ESP_OK) {
239         ret = ESP_ERR_OTA_VALIDATE_FAILED;
240         goto cleanup;
241     }
242
243  cleanup:
244     LIST_REMOVE(it, entries);
245     free(it);
246     return ret;
247 }
248
249 static uint32_t ota_select_crc(const ota_select *s)
250 {
251     return crc32_le(UINT32_MAX, (uint8_t *)&s->ota_seq, 4);
252 }
253
254 static bool ota_select_valid(const ota_select *s)
255 {
256     return s->ota_seq != UINT32_MAX && s->crc == ota_select_crc(s);
257 }
258
259 static esp_err_t rewrite_ota_seq(uint32_t seq, uint8_t sec_id, const esp_partition_t *ota_data_partition)
260 {
261     esp_err_t ret;
262
263     if (sec_id == 0 || sec_id == 1) {
264         s_ota_select[sec_id].ota_seq = seq;
265         s_ota_select[sec_id].crc = ota_select_crc(&s_ota_select[sec_id]);
266         ret = esp_partition_erase_range(ota_data_partition, sec_id * SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE);
267         if (ret != ESP_OK) {
268             return ret;
269         } else {
270             return esp_partition_write(ota_data_partition, SPI_FLASH_SEC_SIZE * sec_id, &s_ota_select[sec_id].ota_seq, sizeof(ota_select));
271         }
272     } else {
273         return ESP_ERR_INVALID_ARG;
274     }
275 }
276
277 static uint8_t get_ota_partition_count(void)
278 {
279     uint16_t ota_app_count = 0;
280     while (esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_MIN + ota_app_count, NULL) != NULL) {
281             assert(ota_app_count < 16 && "must erase the partition before writing to it");
282             ota_app_count++;
283     }
284     return ota_app_count;
285 }
286
287 static esp_err_t esp_rewrite_ota_data(esp_partition_subtype_t subtype)
288 {
289     esp_err_t ret;
290     const esp_partition_t *find_partition = NULL;
291     uint16_t ota_app_count = 0;
292     uint32_t i = 0;
293     uint32_t seq;
294     static spi_flash_mmap_memory_t ota_data_map;
295     const void *result = NULL;
296
297     find_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_OTA, NULL);
298     if (find_partition != NULL) {
299         ota_app_count = get_ota_partition_count();
300         //esp32_idf use two sector for store information about which partition is running
301         //it defined the two sector as ota data partition,two structure ota_select is saved in the two sector
302         //named data in first sector as s_ota_select[0], second sector data as s_ota_select[1]
303         //e.g.
304         //if s_ota_select[0].ota_seq == s_ota_select[1].ota_seq == 0xFFFFFFFF,means ota info partition is in init status
305         //so it will boot factory application(if there is),if there's no factory application,it will boot ota[0] application 
306         //if s_ota_select[0].ota_seq != 0 and s_ota_select[1].ota_seq != 0,it will choose a max seq ,and get value of max_seq%max_ota_app_number
307         //and boot a subtype (mask 0x0F) value is (max_seq - 1)%max_ota_app_number,so if want switch to run ota[x],can use next formulas.
308         //for example, if s_ota_select[0].ota_seq = 4, s_ota_select[1].ota_seq = 5, and there are 8 ota application, 
309         //current running is (5-1)%8 = 4,running ota[4],so if we want to switch to run ota[7],
310         //we should add s_ota_select[0].ota_seq (is 4) to 4 ,(8-1)%8=7,then it will boot ota[7]
311         //if      A=(B - C)%D
312         //then    B=(A + C)%D + D*n ,n= (0,1,2...)
313         //so current ota app sub type id is x , dest bin subtype is y,total ota app count is n
314         //seq will add (x + n*1 + 1 - seq)%n
315         if (SUB_TYPE_ID(subtype) >= ota_app_count) {
316             return ESP_ERR_INVALID_ARG;
317         }
318
319         ret = esp_partition_mmap(find_partition, 0, find_partition->size, SPI_FLASH_MMAP_DATA, &result, &ota_data_map);
320         if (ret != ESP_OK) {
321             result = NULL;
322             return ret;
323         } else {
324             memcpy(&s_ota_select[0], result, sizeof(ota_select));
325             memcpy(&s_ota_select[1], result + SPI_FLASH_SEC_SIZE, sizeof(ota_select));
326             spi_flash_munmap(ota_data_map);
327         }
328
329         if (ota_select_valid(&s_ota_select[0]) && ota_select_valid(&s_ota_select[1])) {
330             seq = OTA_MAX(s_ota_select[0].ota_seq, s_ota_select[1].ota_seq);
331             while (seq > (SUB_TYPE_ID(subtype) + 1) % ota_app_count + i * ota_app_count) {
332                 i++;
333             }
334
335             if (s_ota_select[0].ota_seq >= s_ota_select[1].ota_seq) {
336                 return rewrite_ota_seq((SUB_TYPE_ID(subtype) + 1) % ota_app_count + i * ota_app_count, 1, find_partition);
337             } else {
338                 return rewrite_ota_seq((SUB_TYPE_ID(subtype) + 1) % ota_app_count + i * ota_app_count, 0, find_partition);
339             }
340
341         } else if (ota_select_valid(&s_ota_select[0])) {
342             while (s_ota_select[0].ota_seq > (SUB_TYPE_ID(subtype) + 1) % ota_app_count + i * ota_app_count) {
343                 i++;
344             }
345             return rewrite_ota_seq((SUB_TYPE_ID(subtype) + 1) % ota_app_count + i * ota_app_count, 1, find_partition);
346
347         } else if (ota_select_valid(&s_ota_select[1])) {
348             while (s_ota_select[1].ota_seq > (SUB_TYPE_ID(subtype) + 1) % ota_app_count + i * ota_app_count) {
349                 i++;
350             }
351             return rewrite_ota_seq((SUB_TYPE_ID(subtype) + 1) % ota_app_count + i * ota_app_count, 0, find_partition);
352
353         } else {
354             /* Both OTA slots are invalid, probably because unformatted... */
355             return rewrite_ota_seq(SUB_TYPE_ID(subtype) + 1, 0, find_partition);
356         }
357
358     } else {
359         return ESP_ERR_NOT_FOUND;
360     }
361 }
362
363 esp_err_t esp_ota_set_boot_partition(const esp_partition_t *partition)
364 {
365     const esp_partition_t *find_partition = NULL;
366     if (partition == NULL) {
367         return ESP_ERR_INVALID_ARG;
368     }
369
370     esp_image_metadata_t data;
371     const esp_partition_pos_t part_pos = {
372         .offset = partition->address,
373         .size = partition->size,
374     };
375     if (esp_image_verify(ESP_IMAGE_VERIFY, &part_pos, &data) != ESP_OK) {
376         return ESP_ERR_OTA_VALIDATE_FAILED;
377     }
378
379 #ifdef CONFIG_SECURE_BOOT_ENABLED
380     esp_err_t ret = esp_secure_boot_verify_signature(partition->address, data.image_len);
381     if (ret != ESP_OK) {
382         return ESP_ERR_OTA_VALIDATE_FAILED;
383     }
384 #endif
385     // if set boot partition to factory bin ,just format ota info partition
386     if (partition->type == ESP_PARTITION_TYPE_APP) {
387         if (partition->subtype == ESP_PARTITION_SUBTYPE_APP_FACTORY) {
388             find_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_OTA, NULL);
389             if (find_partition != NULL) {
390                 return esp_partition_erase_range(find_partition, 0, find_partition->size);
391             } else {
392                 return ESP_ERR_NOT_FOUND;
393             }
394         } else {
395             // try to find this partition in flash,if not find it ,return error
396             find_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_OTA, NULL);
397             if (find_partition != NULL) {
398                 return esp_rewrite_ota_data(partition->subtype);
399             } else {
400                 return ESP_ERR_NOT_FOUND;
401             }
402         }
403     } else {
404         return ESP_ERR_INVALID_ARG;
405     }
406 }
407
408 static const esp_partition_t *find_default_boot_partition(void)
409 {
410     // This logic matches the logic of bootloader get_selected_boot_partition() & load_boot_image().
411
412     // Default to factory if present
413     const esp_partition_t *result = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL);
414     if (result != NULL) {
415         return result;
416     }
417
418     // Try first OTA slot if no factory partition
419     for (esp_partition_subtype_t s = ESP_PARTITION_SUBTYPE_APP_OTA_MIN; s != ESP_PARTITION_SUBTYPE_APP_OTA_MAX; s++) {
420         result = esp_partition_find_first(ESP_PARTITION_TYPE_APP, s, NULL);
421         if (result != NULL) {
422             return result;
423         }
424     }
425
426     // Test app slot if present
427     result = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_TEST, NULL);
428     if (result != NULL) {
429         return result;
430     }
431
432     ESP_LOGE(TAG, "invalid partition table, no app partitions");
433     return NULL;
434 }
435
436 const esp_partition_t *esp_ota_get_boot_partition(void)
437 {
438     esp_err_t ret;
439     const esp_partition_t *find_partition = NULL;
440     static spi_flash_mmap_memory_t ota_data_map;
441     const void *result = NULL;
442     uint16_t ota_app_count = 0;
443     find_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_OTA, NULL);
444
445     if (find_partition == NULL) {
446         ESP_LOGE(TAG, "not found ota data");
447         return NULL;
448     }
449
450     ret = esp_partition_mmap(find_partition, 0, find_partition->size, SPI_FLASH_MMAP_DATA, &result, &ota_data_map);
451     if (ret != ESP_OK) {
452         spi_flash_munmap(ota_data_map);
453         ESP_LOGE(TAG, "mmap ota data filed");
454         return NULL;
455     } else {
456         memcpy(&s_ota_select[0], result, sizeof(ota_select));
457         memcpy(&s_ota_select[1], result + 0x1000, sizeof(ota_select));
458         spi_flash_munmap(ota_data_map);
459     }
460     ota_app_count = get_ota_partition_count();
461
462     ESP_LOGD(TAG, "found ota app max = %d", ota_app_count);
463
464     if (s_ota_select[0].ota_seq == 0xFFFFFFFF && s_ota_select[1].ota_seq == 0xFFFFFFFF) {
465         ESP_LOGD(TAG, "finding factory app......");
466         return find_default_boot_partition();
467     } else if (ota_select_valid(&s_ota_select[0]) && ota_select_valid(&s_ota_select[1])) {
468         ESP_LOGD(TAG, "finding ota_%d app......", \
469                  ESP_PARTITION_SUBTYPE_APP_OTA_MIN + ((OTA_MAX(s_ota_select[0].ota_seq, s_ota_select[1].ota_seq) - 1) % ota_app_count));
470
471         return esp_partition_find_first(ESP_PARTITION_TYPE_APP, \
472                                         ESP_PARTITION_SUBTYPE_APP_OTA_MIN + ((OTA_MAX(s_ota_select[0].ota_seq, s_ota_select[1].ota_seq) - 1) % ota_app_count), NULL);
473     } else if (ota_select_valid(&s_ota_select[0])) {
474         ESP_LOGD(TAG, "finding ota_%d app......", \
475                  ESP_PARTITION_SUBTYPE_APP_OTA_MIN + (s_ota_select[0].ota_seq - 1) % ota_app_count);
476
477         return esp_partition_find_first(ESP_PARTITION_TYPE_APP, \
478                                         ESP_PARTITION_SUBTYPE_APP_OTA_MIN + (s_ota_select[0].ota_seq - 1) % ota_app_count, NULL);
479
480     } else if (ota_select_valid(&s_ota_select[1])) {
481         ESP_LOGD(TAG, "finding ota_%d app......", \
482                  ESP_PARTITION_SUBTYPE_APP_OTA_MIN + (s_ota_select[1].ota_seq - 1) % ota_app_count);
483
484         return esp_partition_find_first(ESP_PARTITION_TYPE_APP, \
485                                         ESP_PARTITION_SUBTYPE_APP_OTA_MIN + (s_ota_select[1].ota_seq - 1) % ota_app_count, NULL);
486
487     } else {
488         ESP_LOGE(TAG, "ota data invalid, no current app. Assuming factory");
489         return find_default_boot_partition();
490     }
491 }
492
493
494 const esp_partition_t* esp_ota_get_running_partition(void)
495 {
496     /* Find the flash address of this exact function. By definition that is part
497        of the currently running firmware. Then find the enclosing partition. */
498
499     size_t phys_offs = spi_flash_cache2phys(esp_ota_get_running_partition);
500
501     assert (phys_offs != SPI_FLASH_CACHE2PHYS_FAIL); /* indicates cache2phys lookup is buggy */
502
503     esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP,
504                                                      ESP_PARTITION_SUBTYPE_ANY,
505                                                      NULL);
506     assert(it != NULL); /* has to be at least one app partition */
507
508     while (it != NULL) {
509         const esp_partition_t *p = esp_partition_get(it);
510         if (p->address <= phys_offs && p->address + p->size > phys_offs) {
511             esp_partition_iterator_release(it);
512             return p;
513         }
514         it = esp_partition_next(it);
515     }
516
517     abort(); /* Partition table is invalid or corrupt */
518 }
519
520
521 const esp_partition_t* esp_ota_get_next_update_partition(const esp_partition_t *start_from)
522 {
523     const esp_partition_t *default_ota = NULL;
524     bool next_is_result = false;
525     if (start_from == NULL) {
526         start_from = esp_ota_get_running_partition();
527     } else {
528         start_from = esp_partition_verify(start_from);
529     }
530     assert (start_from != NULL);
531     /* at this point, 'start_from' points to actual partition table data in flash */
532
533
534     /* Two possibilities: either we want the OTA partition immediately after the current running OTA partition, or we
535        want the first OTA partition in the table (for the case when the last OTA partition is the running partition, or
536        if the current running partition is not OTA.)
537
538        This loop iterates subtypes instead of using esp_partition_find, so we
539        get all OTA partitions in a known order (low slot to high slot).
540     */
541
542     for (esp_partition_subtype_t t = ESP_PARTITION_SUBTYPE_APP_OTA_0;
543          t != ESP_PARTITION_SUBTYPE_APP_OTA_MAX;
544          t++) {
545         const esp_partition_t *p = esp_partition_find_first(ESP_PARTITION_TYPE_APP, t, NULL);
546         if (p == NULL) {
547             continue;
548         }
549
550         if (default_ota == NULL) {
551             /* Default to first OTA partition we find,
552                will be used if nothing else matches */
553             default_ota = p;
554         }
555
556         if (p == start_from) {
557             /* Next OTA partition is the one to use */
558             next_is_result = true;
559         }
560         else if (next_is_result) {
561             return p;
562         }
563     }
564
565     return default_ota;
566
567 }