]> rtime.felk.cvut.cz Git - lincan.git/blob - embedded/libs4c/keyval/keyvalpb.c
e1f48b3ece42ed8e959479868179c86126f4d28c
[lincan.git] / embedded / libs4c / keyval / keyvalpb.c
1 /*******************************************************************
2   Key Value Persistent Storage
3
4   keyvalpb.c    - key value parameters block
5
6   (C) Copyright 2003-2005 by Pavel Pisa - Originator
7   (C) Copyright 2004-2005 by Petr Smolik - Originator
8
9   The uLan utilities library can be used, copied and modified under
10   next licenses
11     - GPL - GNU General Public License
12     - LGPL - GNU Lesser General Public License
13     - MPL - Mozilla Public License
14     - and other licenses added by project originators
15   Code can be modified and re-distributed under any combination
16   of the above listed licenses. If contributor does not agree with
17   some of the licenses, he/she can delete appropriate line.
18   Warning, if you delete all lines, you are not allowed to
19   distribute source code and/or binaries utilizing code.
20
21   See files COPYING and README for details.
22
23  *******************************************************************/
24
25 #include <string.h>
26 #include "keyvalpb.h"
27
28 /*
29  * kvpb_memsum - Compute checksum of given memory area
30  * @base: Pointer to the base of of the region
31  * @size: Size of utilized part of the region
32  *
33  * Return Value: Computed checksum value
34  * File: keyvalpb.c
35  */
36 kvpb_sum_t kvpb_memsum(KVPB_DPTRTYPE uint8_t *base, kvpb_size_t size)
37 {
38   KVPB_LOCALDATA kvpb_sum_t sum=0;
39   KVPB_DPTRTYPE uint16_t *p=(KVPB_DPTRTYPE uint16_t *)base;
40   size=(size+1)>>1;
41   while(size--){
42     sum+=*(p++);
43   }
44   sum&=KVPB_SUM_MASK;
45   sum|=KVPB_SUM_OKVAL;
46   return sum;
47 }
48
49 #ifndef KVPB_WITHOUT_HADLE
50 /*
51  * kvpb_get_psum - Get pointer to the region check sum
52  * @kvpb_block: Pointer to the KVPB access information/state structure
53  * @base: Pointer to the base of of the region
54  * @size: Size of one data block region
55  *
56  * Return Value: Pointer to the actual region check sum placement
57  * File: keyvalpb.c
58  */
59 KVPB_DPTRTYPE kvpb_sum_t *kvpb_get_psum(kvpb_block_t *kvpb_block,
60                                   KVPB_DPTRTYPE uint8_t *base, kvpb_size_t size)
61 #else
62 KVPB_DPTRTYPE kvpb_sum_t *__kvpb_get_psum(
63                                   KVPB_DPTRTYPE uint8_t *base, kvpb_size_t size)
64 #endif
65 {
66   KVPB_DPTRTYPE kvpb_sum_t *psum;
67   psum=kvpb_psum_align(kvpb_block,(KVPB_DPTRTYPE kvpb_sum_t*)(base+size)-1);
68   while((KVPB_DPTRTYPE uint8_t*)psum>=base) {
69     if (*kvpb_psum_valid_loc(kvpb_block,psum)!=0)
70       return psum;
71     psum=kvpb_psum_align(kvpb_block,psum-1);
72   }
73   return NULL;
74 }
75
76 #ifndef KVPB_WITHOUT_HADLE
77 /*
78  * kvpb_get_cfk - Get space where to place new key-value pair
79  * @kvpb_block: Pointer to the KVPB access information/state structure
80  * @mode: 0 .. work on active/valid data region;
81  *      1 .. work on the first copy/region, 2 .. work on the second copy/region
82  * @size: Size of required space for stored value
83  *
84  * Return Value: Pointer where next pair should be stored or %NULL
85  * File: keyvalpb.c
86  */
87 KVPB_DPTRTYPE kvpb_key_t *kvpb_get_cfk(kvpb_block_t *kvpb_block,uint8_t mode,int size)
88 #else
89 KVPB_DPTRTYPE kvpb_key_t *__kvpb_get_cfk(uint8_t mode,int size)
90 #endif
91 {
92   KVPB_DPTRTYPE kvpb_sum_t *psum;
93   KVPB_DPTRTYPE uint8_t    *p;
94   KVPB_DPTRTYPE uint8_t    *r;
95   p=kvpb_region_base(kvpb_block,0);
96   size=kvpb_chunk_align(kvpb_block,size+sizeof(kvpb_key_t))+
97        (kvpb_block->flags&KVPB_DESC_CHUNKWO?kvpb_chunk_size(kvpb_block):0);
98   psum=kvpb_block->psum1;
99   if((!mode && (kvpb_block->flags & KVPB_DESC_USE2ND))||(mode==2)) {
100     if(!(kvpb_block->flags&KVPB_DESC_DOUBLE))
101       return NULL;
102     p=kvpb_region_base(kvpb_block,1);
103     psum=kvpb_block->psum2;
104   }
105   do {
106     kvpb_size_t ksize=((KVPB_DPTRTYPE kvpb_key_t *)p)->size;
107     if(ksize==KVPB_EMPTY)
108       break;
109     if(((uint8_t*)psum-(uint8_t*)p)<ksize)
110       return NULL;
111     p+=kvpb_chunk_align(kvpb_block,ksize+sizeof(kvpb_key_t))+
112        (kvpb_block->flags&KVPB_DESC_CHUNKWO?kvpb_chunk_size(kvpb_block):0);
113   } while(1);
114   r=(KVPB_DPTRTYPE uint8_t*)p+size+sizeof(kvpb_key_t);
115   if(r<p)
116     return NULL;
117   if ((uint8_t*)kvpb_psum_align(kvpb_block,psum-1)<r) {
118     return NULL;
119   }
120   return (KVPB_DPTRTYPE kvpb_key_t*)p;
121 }
122
123
124 #ifndef KVPB_WITHOUT_HADLE
125 /**
126  * kvpb_check - Check data consistency of the KVPB storage
127  * @kvpb_block: Pointer to the KVPB access information/state structure
128  * @mode: if mode is nonzero, try to restore valid state or erase all data
129  *
130  * Return Value: 0 .. all regions are correct, 1 .. the first region is valid, the second
131  *      region is invalid or has been updated if @mode has been set, 2 .. the second region
132  *      is valid, the first is invalid or has been updated if @mode has been set, 3 .. both
133  *      regions has been erased and emptied, -1 .. the state is inconsistent and no valid
134  *      region has been found and state has not be corrected
135  * File: keyvalpb.c
136  */
137 int kvpb_check(kvpb_block_t *kvpb_block, uint8_t mode)
138 #else
139 int __kvpb_check(uint8_t mode)
140 #endif
141 {
142   KVPB_DPTRTYPE uint8_t *p;
143   KVPB_LOCALDATA int ret=-1;
144   KVPB_LOCALDATA kvpb_sum_t sum;
145
146   kvpb_block->flags&=~KVPB_DESC_USE2ND;
147
148   p=kvpb_region_base(kvpb_block,0);
149   kvpb_block->psum1=kvpb_get_psum(kvpb_block,p,kvpb_block->size);
150   if (kvpb_block->psum1) {
151     sum=kvpb_memsum(p,(KVPB_DPTRTYPE uint8_t*)kvpb_block->psum1-p);
152     if(*kvpb_block->psum1==sum){
153       ret=1;
154     }
155   }
156
157   if(kvpb_block->flags&KVPB_DESC_DOUBLE){
158     p=kvpb_region_base(kvpb_block,1);
159     kvpb_block->psum2=kvpb_get_psum(kvpb_block,p,kvpb_block->size);
160     if (kvpb_block->psum2) {
161       sum=kvpb_memsum(p,(KVPB_DPTRTYPE uint8_t*)kvpb_block->psum2-p);
162       if(*kvpb_block->psum2==sum) {
163         if(ret>=0){
164           ret=0;
165         } else {
166           ret=2;
167           kvpb_block->flags|=KVPB_DESC_USE2ND;
168         }
169       }
170     }
171   } else {
172     if(ret>=0)
173       ret=0;
174   }
175
176   if(ret){
177     if(!mode) {
178       kvpb_block->flags|=KVPB_DESC_RO;
179     } else {
180       /* correct for FLASH */
181       if(ret<0){
182         p=kvpb_region_base(kvpb_block,0);
183         kvpb_block_erase(kvpb_block,p,kvpb_block->size);
184         kvpb_block->psum1=kvpb_psum_align(kvpb_block,(KVPB_DPTRTYPE kvpb_sum_t*)(p+kvpb_block->size)-1);
185         sum=kvpb_memsum(p,(KVPB_DPTRTYPE uint8_t*)kvpb_block->psum1-p);
186         kvpb_block_copy(kvpb_block,kvpb_block->psum1,&sum,sizeof(kvpb_sum_t));
187         if(kvpb_block->flags&KVPB_DESC_DOUBLE){
188           p=kvpb_region_base(kvpb_block,1);
189           kvpb_block_erase(kvpb_block,p,kvpb_block->size);
190           kvpb_block->psum2=kvpb_psum_align(kvpb_block,(KVPB_DPTRTYPE kvpb_sum_t*)(p+kvpb_block->size)-1);
191           sum=kvpb_memsum(p,(KVPB_DPTRTYPE uint8_t*)kvpb_block->psum2-p);
192           kvpb_block_copy(kvpb_block,kvpb_block->psum2,&sum,sizeof(kvpb_sum_t));
193         }
194         ret=3;
195       }else{
196         if(ret==1){
197           kvpb_block_erase(kvpb_block,kvpb_region_base(kvpb_block,1),kvpb_block->size);
198           kvpb_block_copy(kvpb_block,kvpb_region_base(kvpb_block,1),
199                           kvpb_region_base(kvpb_block,0),kvpb_block->size);
200
201         }else{
202           kvpb_block_erase(kvpb_block,kvpb_region_base(kvpb_block,0),kvpb_block->size);
203           kvpb_block_copy(kvpb_block,kvpb_region_base(kvpb_block,0),
204                           kvpb_region_base(kvpb_block,1),kvpb_block->size);
205         }
206       }
207       kvpb_block->flags&=~KVPB_DESC_RO;
208     }
209   }
210   kvpb_block_flush(kvpb_block);
211   if(ret>=0) kvpb_block->flags|=KVPB_DESC_VALID;
212   return ret;
213 }
214
215 #ifndef KVPB_WITHOUT_HADLE
216 /**
217  * kvpb_first - Get pointer to the first key-value pair in the KVPB storage
218  * @kvpb_block: Pointer to the KVPB access information/state structure
219  * @mode: 0 .. iterate over active/valid data region;
220  *      1 .. iterate over first copy/region, 2 .. iterate over second copy/region
221  *
222  * Return Value: Pointer to the first key-value pair
223  *      or %NULL if no pair exist.
224  * File: keyvalpb.c
225  */
226 KVPB_DPTRTYPE kvpb_key_t *kvpb_first(kvpb_block_t *kvpb_block, uint8_t mode)
227 #else
228 KVPB_DPTRTYPE kvpb_key_t *__kvpb_first(uint8_t mode)
229 #endif
230 {
231   KVPB_DPTRTYPE kvpb_key_t *key=(KVPB_DPTRTYPE kvpb_key_t *)kvpb_region_base(kvpb_block,0);
232   if((!mode && (kvpb_block->flags & KVPB_DESC_USE2ND))||(mode==2)) {
233     if(!(kvpb_block->flags&KVPB_DESC_DOUBLE))
234       return NULL;
235     key=(KVPB_DPTRTYPE kvpb_key_t *)kvpb_region_base(kvpb_block,1);
236   }
237   while(*kvpb_keyid_valid(kvpb_block,key)==KVPB_KEYID_INVALID) {
238     key=kvpb_next(kvpb_block,key);
239     if (!key)
240       return NULL;
241   }
242   return key->size!=KVPB_EMPTY?key:NULL;
243 }
244
245 #ifndef KVPB_WITHOUT_HADLE
246 /**
247  * kvpb_next - Iterate to the next consecutive key-value pair
248  * @kvpb_block: Pointer to the KVPB access information/state structure
249  * @key: Pointer to the previous key-value pair
250  *
251  * Return Value: Pointer to the next key-value pair
252  *      or %NULL if no/no-more pairs exist.
253  * File: keyvalpb.c
254  */
255 KVPB_DPTRTYPE kvpb_key_t *kvpb_next(kvpb_block_t *kvpb_block, KVPB_DPTRTYPE kvpb_key_t *key)
256 #else
257 KVPB_DPTRTYPE kvpb_key_t *__kvpb_next(KVPB_DPTRTYPE kvpb_key_t *key)
258 #endif
259 {
260   do {
261     key=(KVPB_DPTRTYPE kvpb_key_t *)((KVPB_DPTRTYPE uint8_t *)key+
262                                 kvpb_chunk_align(kvpb_block,key->size+sizeof(kvpb_key_t))+
263                                 (kvpb_block->flags&KVPB_DESC_CHUNKWO?kvpb_chunk_size(kvpb_block):0));
264     if (key->size==KVPB_EMPTY) return NULL;
265   } while(*kvpb_keyid_valid(kvpb_block,key)==KVPB_KEYID_INVALID);
266   return key;
267 }
268
269 #ifndef KVPB_WITHOUT_HADLE
270 /**
271  * kvpb_find - Find first of occurrence of given key ID
272  * @kvpb_block: Pointer to the KVPB access information/state structure
273  * @keyid: Ordinal value representing key ID
274  * @mode: iteration mode modifier: 0 .. search in the active/valid data region;
275  *      1 .. search in the first copy/region, 2 .. search in the second copy/region
276  * @key: Previous key occurrence pointer or %NULL value to find first key ID named key-value pair
277  *
278  * Return Value: Pointer to the first on subsequent occurrence of key-value pair addressed by given key ID
279  *      or %NULL if no/no-more occurrences exists.
280  * File: keyvalpb.c
281  */
282 KVPB_DPTRTYPE kvpb_key_t *kvpb_find(kvpb_block_t *kvpb_block, kvpb_keyid_t keyid, uint8_t mode, KVPB_DPTRTYPE kvpb_key_t *key)
283 #else
284 KVPB_DPTRTYPE kvpb_key_t *__kvpb_find(kvpb_keyid_t keyid, uint8_t mode, KVPB_DPTRTYPE kvpb_key_t *key)
285 #endif
286 {
287   if(!(kvpb_block->flags&KVPB_DESC_VALID))
288     return NULL;
289   if (key) {
290       key=kvpb_next(kvpb_block, key);
291   } else {
292       key=kvpb_first(kvpb_block, mode);
293   }
294   while(key) {
295     if((key->keyid==keyid) || (keyid==0))
296       return key;
297     key=kvpb_next(kvpb_block, key);
298   }
299   return key;
300 }
301
302 #ifndef KVPB_WITHOUT_HADLE
303 /**
304  * kvpb_compact_region - Compact one KVPB data block/region
305  * @kvpb_block: Pointer to the KVPB access information/state structure
306  * @keyid: Key ID which should be omitted from compacted data
307  * @mode: 0 .. compact active/valid data region;
308  *      1 .. compact the first data copy, 2 .. compact the second copy
309  *
310  * Return Value: Operation cannot be finished.
311  * File: keyvalpb.c
312  */
313 int kvpb_compact_region(kvpb_block_t *kvpb_block, uint8_t mode, kvpb_keyid_t keyid)
314 #else
315 int __kvpb_compact_region(uint8_t mode, kvpb_keyid_t keyid)
316 #endif
317 {
318   KVPB_DPTRTYPE uint8_t *p;
319   KVPB_DPTRTYPE kvpb_key_t *des,*src;
320
321   p=kvpb_region_base(kvpb_block,0);
322   src=(KVPB_DPTRTYPE kvpb_key_t*)kvpb_region_base(kvpb_block,1);
323   des=(KVPB_DPTRTYPE kvpb_key_t*)p;
324   if((!mode && (kvpb_block->flags & KVPB_DESC_USE2ND))||(mode==2)) {
325     if(!(kvpb_block->flags&KVPB_DESC_DOUBLE))
326       return -1;
327     des=src;
328     src=(KVPB_DPTRTYPE kvpb_key_t*)p;
329     p=(KVPB_DPTRTYPE uint8_t *)des;
330     kvpb_block->psum2=kvpb_psum_align(kvpb_block,
331                       (KVPB_DPTRTYPE kvpb_sum_t*)(p+kvpb_block->size)-1);
332   } else {
333     kvpb_block->psum1=kvpb_psum_align(kvpb_block,
334                     (KVPB_DPTRTYPE kvpb_sum_t*)(p+kvpb_block->size)-1);
335   }
336   kvpb_block_flush(kvpb_block);
337   kvpb_block_erase(kvpb_block,des,kvpb_block->size);
338   while(src) {
339     int s=kvpb_chunk_align(kvpb_block,src->size+sizeof(kvpb_key_t));
340     if((*kvpb_keyid_valid(kvpb_block,src)!=KVPB_KEYID_INVALID) && (src->keyid!=keyid)) {
341       kvpb_block_copy(kvpb_block,des,src,s);
342       if (kvpb_block->flags&KVPB_DESC_CHUNKWO) s+=kvpb_chunk_size(kvpb_block);
343       des=(KVPB_DPTRTYPE kvpb_key_t*)((uint8_t*)des+s);
344     }
345     src=kvpb_next(kvpb_block, src);
346   }
347   kvpb_block_flush(kvpb_block);
348   return 0;
349 }
350
351 #ifndef KVPB_WITHOUT_HADLE
352 /**
353  * kvpb_get_key - Get value for given key ID
354  * @kvpb_block: Pointer to the KVPB access information/state structure
355  * @keyid: Ordinal value representing key ID
356  * @size: The size of the buffer provided to store data into
357  * @buf: Pointer to the buffer, where retrieved data should be copied
358  *
359  * Return Value: Number of retrieved value bytes if operation is successful
360  *      or -1 if there is no such key ID or operation fails for other reason.
361  * File: keyvalpb.c
362  */
363 int kvpb_get_key(kvpb_block_t *kvpb_block, kvpb_keyid_t keyid, kvpb_size_t size, void *buf)
364 #else
365 int __kvpb_get_key(kvpb_keyid_t keyid, kvpb_size_t size, void *buf)
366 #endif
367 {
368   KVPB_DPTRTYPE kvpb_key_t *key;
369   key=kvpb_find(kvpb_block,keyid,0,NULL);
370   if(!key) return -1;
371   if(size && buf){
372     if(key->size<size)
373       size=key->size;
374     memcpy(buf,key+1,size);
375   }
376   return key->size;
377 }
378
379
380 #ifndef KVPB_WITHOUT_HADLE
381 /**
382  * kvpb_set_key - Set new value or define new key-value pair
383  * @kvpb_block: Pointer to the KVPB access information/state structure
384  * @keyid: Ordinal value representing key ID, if or-red with %KVPB_KEYID_DUPLIC,
385  *      the key ID can be defined/inserted  multiple times
386  * @size: Stored value size in bytes
387  * @buf: Pointer to the stored value data
388  *
389  * Return Value: Number of stored bytes (equal to @size) if operation is successful
390  *      or -1 if operation fails.
391  * File: keyvalpb.c
392  */
393 int kvpb_set_key(kvpb_block_t *kvpb_block, kvpb_keyid_t keyid, kvpb_size_t size, const void *buf)
394 #else
395 int __kvpb_set_key(kvpb_keyid_t keyid, kvpb_size_t size, const void *buf)
396 #endif
397 {
398   KVPB_LOCALDATA kvpb_sum_t sum;
399   KVPB_DPTRTYPE kvpb_sum_t *psum;
400   KVPB_DPTRTYPE kvpb_key_t *key;
401   KVPB_DPTRTYPE uint8_t *p;
402
403   if(!(kvpb_block->flags&KVPB_DESC_VALID))
404     return -1;
405   if(kvpb_block->flags&KVPB_DESC_RO)
406     return -1;
407
408   /*first region*/
409   psum=kvpb_psum_align(kvpb_block,kvpb_block->psum1);
410   sum=0;
411   kvpb_block_copy(kvpb_block,kvpb_psum_valid_loc(kvpb_block,psum),&sum,sizeof(kvpb_sum_t));
412   kvpb_block->psum1=kvpb_psum_align(kvpb_block,kvpb_block->psum1-1);
413   if (!(keyid&KVPB_KEYID_DUPLIC) || !buf) {
414     kvpb_each_from(kvpb_block,keyid,1,key) {
415       kvpb_keyid_t dkeyid=KVPB_KEYID_INVALID;
416       kvpb_block_copy(kvpb_block,kvpb_keyid_valid(kvpb_block,key),&dkeyid,sizeof(kvpb_keyid_t));
417     }
418   }
419   key=kvpb_get_cfk(kvpb_block,1,size);
420   if (!key) {
421     kvpb_compact_region(kvpb_block,1,(keyid&KVPB_KEYID_DUPLIC)?0:keyid);
422     key=kvpb_get_cfk(kvpb_block,1,size);
423   }
424   if (keyid && key && buf) {
425     kvpb_block_copy(kvpb_block,&key->size,&size,sizeof(kvpb_size_t));
426     kvpb_block_copy(kvpb_block,&key->keyid,&keyid,sizeof(kvpb_keyid_t));
427     kvpb_block_copy(kvpb_block,(uint8_t*)(key+1),buf,/*align???*/ size);
428   }
429   /* need flush data to count correct value of new check sum */
430   kvpb_block_flush(kvpb_block);
431
432   p=kvpb_region_base(kvpb_block,0);
433   sum=kvpb_memsum(p,(KVPB_DPTRTYPE uint8_t*)kvpb_block->psum1-p);
434   kvpb_block_copy(kvpb_block,kvpb_block->psum1,&sum,sizeof(kvpb_sum_t));
435   kvpb_block_flush(kvpb_block);
436   if(!(kvpb_block->flags&KVPB_DESC_DOUBLE))
437     return key?size:-1;
438
439   /*Write in the first region failed, switching to backup region */
440   if(kvpb_block->flags&KVPB_DESC_RO){
441     kvpb_block->flags|=KVPB_DESC_USE2ND;
442     return -1;
443   }
444
445   /*second region*/
446   psum=kvpb_psum_align(kvpb_block,kvpb_block->psum2);
447   sum=0;
448   kvpb_block_copy(kvpb_block,kvpb_psum_valid_loc(kvpb_block,psum),&sum,sizeof(kvpb_sum_t));
449   kvpb_block->psum2=kvpb_psum_align(kvpb_block,kvpb_block->psum2-1);
450   if (!(keyid&KVPB_KEYID_DUPLIC) || !buf) {
451     kvpb_each_from(kvpb_block,keyid,2,key) {
452       kvpb_keyid_t dkeyid=KVPB_KEYID_INVALID;
453       kvpb_block_copy(kvpb_block,kvpb_keyid_valid(kvpb_block,key),&dkeyid,sizeof(kvpb_keyid_t));
454     }
455   }
456   key=kvpb_get_cfk(kvpb_block,2,size);
457   if (!key) {
458     kvpb_compact_region(kvpb_block,2,(keyid&KVPB_KEYID_DUPLIC)?0:keyid);
459     key=kvpb_get_cfk(kvpb_block,2,size);
460   }
461   if (keyid && key && buf) {
462     kvpb_block_copy(kvpb_block,&key->size,&size,sizeof(kvpb_size_t));
463     kvpb_block_copy(kvpb_block,&key->keyid,&keyid,sizeof(kvpb_keyid_t));
464     kvpb_block_copy(kvpb_block,(uint8_t*)(key+1),buf,/*align???*/ size);
465   }
466   kvpb_block_flush(kvpb_block);
467
468   p=kvpb_region_base(kvpb_block,1);
469   sum=kvpb_memsum(p,(KVPB_DPTRTYPE uint8_t*)kvpb_block->psum2-p);
470   kvpb_block_copy(kvpb_block,kvpb_block->psum2,&sum,sizeof(kvpb_sum_t));
471   kvpb_block_flush(kvpb_block);
472   /*Write in the second region failed, switching to the first region */
473   if(kvpb_block->flags&KVPB_DESC_RO){
474     kvpb_block->flags&=~KVPB_DESC_USE2ND;
475     return -1;
476   }
477
478   return key?size:-1;
479 }
480
481 #ifndef KVPB_MINIMALIZED
482 /**
483  * kvpb_err_keys - Erase/delete key-value pair
484  * @kvpb_block: Pointer to the KVPB access information/state structure
485  * @keyid: Ordinal value representing key ID
486  *
487  * Return Value: Positive or zero value informs about successful operation,
488  *      -1 if operation fails.
489  * File: keyvalpb.c
490  */
491 int kvpb_err_keys(kvpb_block_t *kvpb_block, kvpb_keyid_t keyid)
492 {
493   return kvpb_set_key(kvpb_block,keyid,0,NULL);
494 }
495 #endif  /*KVPB_MINIMALIZED*/
496