]> git.cryptolib.org Git - avr-crypto-lib.git/blob - test_src/cmacvs.c
fixing E-Mail-Address & Copyright
[avr-crypto-lib.git] / test_src / cmacvs.c
1 /* cmacvs.c */
2 /*
3     This file is part of the AVR-Crypto-Lib.
4     Copyright (C) 2006-2015 Daniel Otte (bg@nerilex.org)
5
6     This program is free software: you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation, either version 3 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 /**
20  * \file        cmacvs.c
21  * \author  Daniel Otte
22  * \date    2010-02-02
23  * \license     GPLv3 or later
24  *
25  */
26
27 #include <avr/pgmspace.h>
28 #include <stdint.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <ctype.h>
32 #include "blockcipher_descriptor.h"
33 #include "bcal-basic.h"
34 #include "bcal-cmac.h"
35 #include "cmacvs.h"
36 #include "string-extras.h"
37 #include "cli.h"
38
39
40 #ifdef DEBUG
41 #  undef DEBUG
42 #endif
43
44 #define DEBUG 0
45
46 #if DEBUG
47 #  include "config.h"
48 #  include <util/delay.h>
49 #endif
50
51 bcdesc_t*  cmacvs_algo=NULL;
52 bcdesc_t** cmacvs_algolist=NULL;
53
54 void cmacvs_listalgos(void){
55         char option = 'a';
56
57         bcdesc_t *t;
58         uint8_t i=0;
59         cli_putstr_P(PSTR("\r\nthe following algorithms are available:\r\n"));
60         while(option<='z' && (t=(bcdesc_t*)pgm_read_word(&(cmacvs_algolist[i])))){
61                 cli_putc('\t');
62                 cli_putc((t==cmacvs_algo)?'*':' ');
63                 cli_putc(option++);
64                 cli_putstr_P(PSTR(":\t"));
65                 cli_putstr_P((void*)(pgm_read_word(&(t->name))));
66                 cli_putstr_P(PSTR("\r\n"));
67                 i++;
68         }
69 }
70
71 void cmacvs_setalgo(char *param){
72         param = strstrip(param);
73         if(param[1]=='\0'){ /* single letter specified */
74                 uint8_t i,option = param[0]-'a';
75
76                 if(!cmacvs_algolist){
77                         cli_putstr_P(PSTR("\r\nERROR: cmacvs_algolist not set!"));
78                         return;
79                 }
80                 for(i=0; i<=option; ++i){
81                         if((void*)pgm_read_word(&(cmacvs_algolist[i]))==NULL){
82                                 cli_putstr_P(PSTR("\r\nERROR: invalid selection!"));
83                                 return;
84                         }
85                 }
86                 cmacvs_algo=(bcdesc_t*)pgm_read_word(&(cmacvs_algolist[option]));
87         } else { /* name specifyed */
88                 bcdesc_t *t=NULL;
89                 uint8_t i=0;
90                 while((t=(bcdesc_t*)pgm_read_word(&(cmacvs_algolist[i]))) &&
91                        strcasecmp_P(param, (void*)pgm_read_word(&(t->name))))
92                         ++i;
93                 if(t){
94                         cmacvs_algo=t;
95                 }else{
96                         cli_putstr_P(PSTR("\r\nERROR: could not find \""));
97                         cli_putstr(param);
98                         cli_putstr_P(PSTR("\"!"));
99                 }
100         }
101 }
102
103 typedef struct {
104         uint16_t buffer_idx;
105         uint16_t buffersize_B;
106         uint32_t blocks;
107         bcal_cmac_ctx_t ctx;
108         uint8_t *buffer;
109         uint8_t  in_byte;
110 } cmacvs_ctx_t;
111
112 static cmacvs_ctx_t cmacvs_ctx;
113
114 uint8_t buffer_add(char c){
115         uint8_t v,t;
116         if(cmacvs_ctx.buffer_idx==cmacvs_ctx.buffersize_B){
117                 bcal_cmac_nextBlock(&(cmacvs_ctx.ctx), cmacvs_ctx.buffer);
118                 ++cmacvs_ctx.blocks;
119                 cmacvs_ctx.buffer_idx=0;
120                 cmacvs_ctx.in_byte=0;
121                 cli_putc('.');
122                 memset(cmacvs_ctx.buffer, 0, cmacvs_ctx.buffersize_B);
123         }
124         if(c>='0' && c<='9'){
125                 v=c-'0';
126         }else{
127                 c &= (uint8_t)~('a' ^ 'A');
128                 if(c>='A' && c<='F'){
129                         v=c-'A'+10;
130                 }else{
131                         return 1;
132                 }
133         }
134         t=cmacvs_ctx.buffer[cmacvs_ctx.buffer_idx];
135         if(cmacvs_ctx.in_byte){
136                 t |= v;
137                 cmacvs_ctx.buffer[cmacvs_ctx.buffer_idx]=t;
138                 cmacvs_ctx.buffer_idx++;
139                 cmacvs_ctx.in_byte = 0;
140         }else{
141                 t |= v<<4;
142                 cmacvs_ctx.buffer[cmacvs_ctx.buffer_idx]=t;
143                 cmacvs_ctx.in_byte = 1;
144         }
145         return 0;
146 }
147
148 int32_t getValue_P(PGM_P key){
149         uint32_t val=0;
150         char instr[21];
151         char *str2;
152         for(;;){
153                 memset(instr, 0, 21);
154                 cli_getsn_cecho(instr, 20);
155                 str2 = strstrip(instr);
156                 if(!strncasecmp_P(str2, key, strlen_P(key))){
157                         while(*str2 && *str2!='=')
158                                 str2++;
159                         if(*str2=='='){
160                                 do{
161                                         str2++;
162                                 }while(*str2 && !isdigit(*str2));
163                                 val=(uint32_t)strtoul(str2, NULL, 10);
164                                 return val;
165                         }
166                 } else {
167                         if(!strncasecmp_P(str2, PSTR("EXIT"), 4)){
168                                 cli_putstr_P(PSTR("\r\n got exit ..."));
169                                 return -1;
170                         }
171                 }
172         }
173         return -2;
174 }
175
176 uint8_t getKey(void *key_buffer, uint8_t klen_B){
177         char c;
178         uint8_t v,i=0;
179         memset(key_buffer, 0x00, klen_B);
180         do{
181                 c = cli_getc_cecho();
182         }while((c|('a'^'A'))!='k');
183         do{
184                 c = cli_getc_cecho();
185         }while((c|('a'^'A'))!='e');
186         do{
187                 c = cli_getc_cecho();
188         }while((c|('a'^'A'))!='y');
189         do{
190                 c = cli_getc_cecho();
191         }while(c!='=');
192         klen_B *= 2;
193         while(klen_B){
194                 v = 0x10;
195                 c = cli_getc_cecho();
196                 if(c>='0' && c<='9'){
197                         v = c-'0';
198                 }else{
199                         c |= 'A'^'a';
200                         if(c>='a' && c<='f'){
201                                 v= c-'a'+10;
202                         }
203                 }
204                 if(v<0x10){
205                         if((i&1)==0){
206                                 v<<=4;
207                         }
208                         ((uint8_t*)key_buffer)[i/2] |= v;
209                         ++i;
210                         --klen_B;
211                 }
212         }
213         return 0;
214 }
215
216 uint8_t getMac(void *mac_buffer, uint8_t mlen_B){
217         char c;
218         uint8_t v,i=0;
219         memset(mac_buffer, 0x00, mlen_B);
220         do{
221                 c = cli_getc_cecho();
222         }while((c|('a'^'A'))!='m');
223         do{
224                 c = cli_getc_cecho();
225         }while((c|('a'^'A'))!='a');
226         do{
227                 c = cli_getc_cecho();
228         }while((c|('a'^'A'))!='c');
229         do{
230                 c = cli_getc_cecho();
231         }while(c!='=');
232         mlen_B *= 2;
233         while(mlen_B){
234                 v = 0x10;
235                 c = cli_getc_cecho();
236                 if(c>='0' && c<='9'){
237                         v = c-'0';
238                 }else{
239                         c |= 'A'^'a';
240                         if(c>='a' && c<='f'){
241                                 v= c-'a'+10;
242                         }
243                 }
244                 if(v<0x10){
245                         if((i&1)==0){
246                                 v<<=4;
247                         }
248                         ((uint8_t*)mac_buffer)[i/2] |= v;
249                         ++i;
250                         --mlen_B;
251                 }
252         }
253         return 0;
254 }
255
256 void cmacvs_test1(void){ /* Gen tests */
257         int32_t klen, mlen, tlen;
258         int32_t expect_input=0;
259
260         if(!cmacvs_algo){
261                         cli_putstr_P(PSTR("\r\nERROR: select algorithm first!"));
262                 return;
263         }
264         char c;
265         cmacvs_ctx.buffersize_B=pgm_read_word(&(cmacvs_algo->blocksize_b))/8;
266         uint8_t tag[cmacvs_ctx.buffersize_B];
267         uint8_t buffer[cmacvs_ctx.buffersize_B+5];
268         cmacvs_ctx.buffer = buffer;
269         cli_putstr_P(PSTR("\r\nbuffer_size = 0x"));
270         cli_hexdump_rev(&(cmacvs_ctx.buffersize_B), 2);
271         cli_putstr_P(PSTR(" bytes"));
272         for(;;){
273                 cmacvs_ctx.blocks = 0;
274                 memset(buffer, 0, cmacvs_ctx.buffersize_B);
275                 klen = getValue_P(PSTR("Klen"));
276                 if(klen<0){
277                         return;
278                 }
279                 mlen = getValue_P(PSTR("Mlen"));
280                 if(mlen<0){
281                         return;
282                 }
283                 tlen = getValue_P(PSTR("Tlen"));
284                 if(tlen<0){
285                         return;
286                 }
287                 uint8_t key_buffer[klen];
288 #if DEBUG
289                 cli_putstr_P(PSTR("\r\nKLen == "));
290                 cli_hexdump_rev(&klen, 4);
291                 cli_putstr_P(PSTR("\r\nMLen == "));
292                 cli_hexdump_rev(&mlen, 4);
293                 cli_putstr_P(PSTR("\r\nTLen == "));
294                 cli_hexdump_rev(&tlen, 4);
295 #endif
296                 getKey(key_buffer, klen);
297                 if(mlen==0){
298                         expect_input=2;
299                 }else{
300                         expect_input=mlen*2;
301                 }
302 #if DEBUG
303                 cli_putstr_P(PSTR("\r\nexpected_input == "));
304                 cli_hexdump_rev(&expect_input, 4);
305                 if(expect_input==0)
306                         cli_putstr_P(PSTR("\r\nexpected_input == 0 !!!"));
307 #endif
308                 uint8_t ret;
309 #if DEBUG
310                 cli_putstr_P(PSTR("\r\n CMAC init"));
311                 cli_putstr_P(PSTR("\r\n (2) expected_input == "));
312                 cli_hexdump_rev(&expect_input, 4);
313 #endif
314                 ret = bcal_cmac_init(cmacvs_algo, key_buffer, klen*8, &(cmacvs_ctx.ctx));
315                 if(ret){
316                         cli_putstr_P(PSTR("\r\n bcal_cmac_init returned with: "));
317                         cli_hexdump(&ret, 1);
318                         return;
319                 }
320 #if DEBUG
321                 cli_putstr_P(PSTR("\r\n (3) expected_input == "));
322                 cli_hexdump_rev(&expect_input, 4);
323                 cli_putstr_P(PSTR("\r\n"));
324 #endif
325                 while((c=cli_getc_cecho())!='M' && c!='m'){
326                         if(!isspace(c)){
327                                 cli_putstr_P(PSTR("\r\nERROR: wrong input (1) [0x"));
328                                 cli_hexdump(&c, 1);
329                                 cli_putstr_P(PSTR("]!\r\n"));
330                                 bcal_cmac_free(&(cmacvs_ctx.ctx));
331                                 return;
332                         }
333                 }
334                 if((c=cli_getc_cecho())!='s' && c!='S'){
335                                 cli_putstr_P(PSTR("\r\nERROR: wrong input (2)!\r\n"));
336                                 bcal_cmac_free(&(cmacvs_ctx.ctx));
337                                 return;
338                 }
339                 if((c=cli_getc_cecho())!='g' && c!='G'){
340                                 cli_putstr_P(PSTR("\r\nERROR: wrong input (3)!\r\n"));
341                                 bcal_cmac_free(&(cmacvs_ctx.ctx));
342                                 return;
343                 }
344                 while((c=cli_getc_cecho())!='='){
345                         if(!isspace(c)){
346                                 cli_putstr_P(PSTR("\r\nERROR: wrong input (4)!\r\n"));
347                                 bcal_cmac_free(&(cmacvs_ctx.ctx));
348                                 return;
349                         }
350                 }
351 #if DEBUG
352                 cli_putstr_P(PSTR("\r\nparsing started"));
353 #endif
354                 cmacvs_ctx.buffer_idx = 0;
355                 cmacvs_ctx.in_byte    = 0;
356                 cmacvs_ctx.blocks     = 0;
357                 while(expect_input>0){
358                         c=cli_getc_cecho();
359 #if DEBUG
360                         cli_putstr_P(PSTR("\r\n\t("));
361                         cli_hexdump_rev(&expect_input, 4);
362                         cli_putstr_P(PSTR(") "));
363                         _delay_ms(500);
364 #endif
365                         if(buffer_add(c)==0){
366                                 --expect_input;
367                         }else{
368                                 if(!isblank((uint16_t)c)){
369                                         cli_putstr_P(PSTR("\r\nERROR: wrong input (5) ("));
370                                         cli_putc(c);
371                                         cli_putstr_P(PSTR(")!\r\n"));
372                                         bcal_cmac_free(&(cmacvs_ctx.ctx));
373                                         return;
374                                 }
375                         }
376                 }
377 #if DEBUG
378                 cli_putstr_P(PSTR("\r\nBuffer-A:"));
379                 cli_hexdump_block(buffer, cmacvs_ctx.buffersize_B, 5, 8);
380
381                 cli_putstr_P(PSTR("\r\n starting finalisation"));
382                 cli_putstr_P(PSTR("\r\n\tblocks     == "));
383                 cli_hexdump_rev(&(cmacvs_ctx.blocks),4);
384                 cli_putstr_P(PSTR("\r\n\tbuffer_idx == "));
385                 cli_hexdump_rev(&(cmacvs_ctx.buffer_idx),2);
386                 cli_putstr_P(PSTR("\r\n\tin_byte    == "));
387                 cli_hexdump_rev(&(cmacvs_ctx.in_byte),1);
388 //              _delay_ms(500);
389
390                 cli_putstr_P(PSTR("\r\n starting last block"));
391                 cli_putstr_P(PSTR("\r\n\tlength       == "));
392                 cli_hexdump_rev(&mlen,4);
393                 cli_putstr_P(PSTR("\r\n\tbuffersize_B == "));
394                 cli_hexdump_rev(&(cmacvs_ctx.buffersize_B),2);
395                 uint16_t temp=(mlen-cmacvs_ctx.blocks*cmacvs_ctx.buffersize_B)*8;
396                 cli_putstr_P(PSTR("\r\n\t (temp)      == "));
397                 cli_hexdump_rev(&temp,2);
398 //              _delay_ms(500);
399 #endif
400                 uint16_t temp=(mlen-cmacvs_ctx.blocks*cmacvs_ctx.buffersize_B)*8;
401                 bcal_cmac_lastBlock( &(cmacvs_ctx.ctx), buffer, /* be aware of freaking compilers!!! */
402 //                                                      length-(cmacvs_ctx.blocks)*((cmacvs_ctx.buffersize_B)*8));
403                                     temp );
404 #if DEBUG
405                 cli_putstr_P(PSTR("\r\n starting ctx2cmac"));
406                 _delay_ms(500);
407 #endif
408                 bcal_cmac_ctx2mac(tag, tlen*8, &(cmacvs_ctx.ctx));
409 #if DEBUG
410                 cli_putstr_P(PSTR("\r\n starting cmac free"));
411 #endif
412                 bcal_cmac_free(&(cmacvs_ctx.ctx));
413                 cli_putstr_P(PSTR("\r\n Mac = "));
414                 cli_hexdump(tag, tlen);
415
416         }
417 }
418
419
420 void cmacvs_test2(void){ /* Ver tests */
421         int32_t klen, mlen, tlen;
422         int32_t expect_input=0;
423
424         if(!cmacvs_algo){
425                         cli_putstr_P(PSTR("\r\nERROR: select algorithm first!"));
426                 return;
427         }
428         char c;
429         cmacvs_ctx.buffersize_B=pgm_read_word(&(cmacvs_algo->blocksize_b))/8;
430         uint8_t tag[cmacvs_ctx.buffersize_B];
431         uint8_t tag_ref[cmacvs_ctx.buffersize_B];
432         uint8_t buffer[cmacvs_ctx.buffersize_B+5];
433         cmacvs_ctx.buffer = buffer;
434         cli_putstr_P(PSTR("\r\nbuffer_size = 0x"));
435         cli_hexdump_rev(&(cmacvs_ctx.buffersize_B), 2);
436         cli_putstr_P(PSTR(" bytes"));
437         for(;;){
438                 cmacvs_ctx.blocks = 0;
439                 memset(buffer, 0, cmacvs_ctx.buffersize_B);
440                 klen = getValue_P(PSTR("Klen"));
441                 if(klen<0){
442                         return;
443                 }
444                 mlen = getValue_P(PSTR("Mlen"));
445                 if(mlen<0){
446                         return;
447                 }
448                 tlen = getValue_P(PSTR("Tlen"));
449                 if(tlen<0){
450                         return;
451                 }
452                 uint8_t key_buffer[klen];
453 #if DEBUG
454                 cli_putstr_P(PSTR("\r\nKLen == "));
455                 cli_hexdump_rev(&klen, 4);
456                 cli_putstr_P(PSTR("\r\nMLen == "));
457                 cli_hexdump_rev(&mlen, 4);
458                 cli_putstr_P(PSTR("\r\nTLen == "));
459                 cli_hexdump_rev(&tlen, 4);
460 #endif
461                 getKey(key_buffer, klen);
462                 if(mlen==0){
463                         expect_input=2;
464                 }else{
465                         expect_input=mlen*2;
466                 }
467 #if DEBUG
468                 cli_putstr_P(PSTR("\r\nexpected_input == "));
469                 cli_hexdump_rev(&expect_input, 4);
470                 if(expect_input==0)
471                         cli_putstr_P(PSTR("\r\nexpected_input == 0 !!!"));
472 #endif
473                 uint8_t ret;
474 #if DEBUG
475                 cli_putstr_P(PSTR("\r\n CMAC init"));
476                 cli_putstr_P(PSTR("\r\n (2) expected_input == "));
477                 cli_hexdump_rev(&expect_input, 4);
478 #endif
479                 ret = bcal_cmac_init(cmacvs_algo, key_buffer, klen*8, &(cmacvs_ctx.ctx));
480                 if(ret){
481                         cli_putstr_P(PSTR("\r\n bcal_cmac_init returned with: "));
482                         cli_hexdump(&ret, 1);
483                         return;
484                 }
485 #if DEBUG
486                 cli_putstr_P(PSTR("\r\n (3) expected_input == "));
487                 cli_hexdump_rev(&expect_input, 4);
488                 cli_putstr_P(PSTR("\r\n"));
489 #endif
490                 while((c=cli_getc_cecho())!='M' && c!='m'){
491                         if(!isspace(c)){
492                                 cli_putstr_P(PSTR("\r\nERROR: wrong input (1) [0x"));
493                                 cli_hexdump(&c, 1);
494                                 cli_putstr_P(PSTR("]!\r\n"));
495                                 bcal_cmac_free(&(cmacvs_ctx.ctx));
496                                 return;
497                         }
498                 }
499                 if((c=cli_getc_cecho())!='s' && c!='S'){
500                                 cli_putstr_P(PSTR("\r\nERROR: wrong input (2)!\r\n"));
501                                 bcal_cmac_free(&(cmacvs_ctx.ctx));
502                                 return;
503                 }
504                 if((c=cli_getc_cecho())!='g' && c!='G'){
505                                 cli_putstr_P(PSTR("\r\nERROR: wrong input (3)!\r\n"));
506                                 bcal_cmac_free(&(cmacvs_ctx.ctx));
507                                 return;
508                 }
509                 while((c=cli_getc_cecho())!='='){
510                         if(!isspace(c)){
511                                 cli_putstr_P(PSTR("\r\nERROR: wrong input (4)!\r\n"));
512                                 bcal_cmac_free(&(cmacvs_ctx.ctx));
513                                 return;
514                         }
515                 }
516 #if DEBUG
517                 cli_putstr_P(PSTR("\r\nparsing started"));
518 #endif
519                 cmacvs_ctx.buffer_idx = 0;
520                 cmacvs_ctx.in_byte    = 0;
521                 cmacvs_ctx.blocks     = 0;
522                 while(expect_input>0){
523                         c=cli_getc_cecho();
524 #if DEBUG
525                         cli_putstr_P(PSTR("\r\n\t("));
526                         cli_hexdump_rev(&expect_input, 4);
527                         cli_putstr_P(PSTR(") "));
528                         _delay_ms(500);
529 #endif
530                         if(buffer_add(c)==0){
531                                 --expect_input;
532                         }else{
533                                 if(!isblank((uint16_t)c)){
534                                         cli_putstr_P(PSTR("\r\nERROR: wrong input (5) ("));
535                                         cli_putc(c);
536                                         cli_putstr_P(PSTR(")!\r\n"));
537                                         bcal_cmac_free(&(cmacvs_ctx.ctx));
538                                         return;
539                                 }
540                         }
541                 }
542 #if DEBUG
543                 cli_putstr_P(PSTR("\r\nBuffer-A:"));
544                 cli_hexdump_block(buffer, cmacvs_ctx.buffersize_B, 5, 8);
545
546                 cli_putstr_P(PSTR("\r\n starting finalisation"));
547                 cli_putstr_P(PSTR("\r\n\tblocks     == "));
548                 cli_hexdump_rev(&(cmacvs_ctx.blocks),4);
549                 cli_putstr_P(PSTR("\r\n\tbuffer_idx == "));
550                 cli_hexdump_rev(&(cmacvs_ctx.buffer_idx),2);
551                 cli_putstr_P(PSTR("\r\n\tin_byte    == "));
552                 cli_hexdump_rev(&(cmacvs_ctx.in_byte),1);
553 //              _delay_ms(500);
554
555                 cli_putstr_P(PSTR("\r\n starting last block"));
556                 cli_putstr_P(PSTR("\r\n\tlength       == "));
557                 cli_hexdump_rev(&mlen,4);
558                 cli_putstr_P(PSTR("\r\n\tbuffersize_B == "));
559                 cli_hexdump_rev(&(cmacvs_ctx.buffersize_B),2);
560                 uint16_t temp=(mlen-cmacvs_ctx.blocks*cmacvs_ctx.buffersize_B)*8;
561                 cli_putstr_P(PSTR("\r\n\t (temp)      == "));
562                 cli_hexdump_rev(&temp,2);
563 //              _delay_ms(500);
564 #endif
565                 uint16_t temp=(mlen-cmacvs_ctx.blocks*cmacvs_ctx.buffersize_B)*8;
566                 bcal_cmac_lastBlock( &(cmacvs_ctx.ctx), buffer, /* be aware of freaking compilers!!! */
567 //                                                      length-(cmacvs_ctx.blocks)*((cmacvs_ctx.buffersize_B)*8));
568                                     temp );
569 #if DEBUG
570                 cli_putstr_P(PSTR("\r\n starting ctx2cmac"));
571                 _delay_ms(500);
572 #endif
573                 bcal_cmac_ctx2mac(tag, tlen*8, &(cmacvs_ctx.ctx));
574 #if DEBUG
575                 cli_putstr_P(PSTR("\r\n starting cmac free"));
576 #endif
577                 bcal_cmac_free(&(cmacvs_ctx.ctx));
578                 cli_putstr_P(PSTR("\r\n Mac = "));
579                 cli_hexdump(tag, tlen);
580                 getMac(tag_ref, tlen);
581                 if(memcmp(tag, tag_ref, tlen)){
582                         cli_putstr_P(PSTR("\r\n Result = F"));
583                 }else{
584                         cli_putstr_P(PSTR("\r\n Result = P"));
585                 }
586         }
587 }