quartz: Stop DSound buffer playback when the filter is paused or stopped, not the...
[wine] / dlls / crypt32 / serialize.c
1 /*
2  * Copyright 2004-2006 Juan Lang
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 #include <stdarg.h>
19 #include "windef.h"
20 #include "winbase.h"
21 #include "wincrypt.h"
22 #include "wine/debug.h"
23 #include "excpt.h"
24 #include "wine/exception.h"
25 #include "crypt32_private.h"
26
27 WINE_DEFAULT_DEBUG_CHANNEL(crypt);
28
29 /* An extended certificate property in serialized form is prefixed by this
30  * header.
31  */
32 typedef struct _WINE_CERT_PROP_HEADER
33 {
34     DWORD propID;
35     DWORD unknown; /* always 1 */
36     DWORD cb;
37 } WINE_CERT_PROP_HEADER, *PWINE_CERT_PROP_HEADER;
38
39 static BOOL CRYPT_SerializeStoreElement(const void *context,
40  const BYTE *encodedContext, DWORD cbEncodedContext, DWORD contextPropID,
41  PCWINE_CONTEXT_INTERFACE contextInterface, DWORD dwFlags, BOOL omitHashes,
42  BYTE *pbElement, DWORD *pcbElement)
43 {
44     BOOL ret;
45
46     TRACE("(%p, %p, %08x, %d, %p, %p)\n", context, contextInterface, dwFlags,
47      omitHashes, pbElement, pcbElement);
48
49     if (context)
50     {
51         DWORD bytesNeeded = sizeof(WINE_CERT_PROP_HEADER) + cbEncodedContext;
52         DWORD prop = 0;
53
54         ret = TRUE;
55         do {
56             prop = contextInterface->enumProps(context, prop);
57             if (prop && (!omitHashes || !IS_CERT_HASH_PROP_ID(prop)))
58             {
59                 DWORD propSize = 0;
60
61                 ret = contextInterface->getProp(context, prop, NULL, &propSize);
62                 if (ret)
63                     bytesNeeded += sizeof(WINE_CERT_PROP_HEADER) + propSize;
64             }
65         } while (ret && prop != 0);
66
67         if (!pbElement)
68         {
69             *pcbElement = bytesNeeded;
70             ret = TRUE;
71         }
72         else if (*pcbElement < bytesNeeded)
73         {
74             *pcbElement = bytesNeeded;
75             SetLastError(ERROR_MORE_DATA);
76             ret = FALSE;
77         }
78         else
79         {
80             PWINE_CERT_PROP_HEADER hdr;
81             DWORD bufSize = 0;
82             LPBYTE buf = NULL;
83
84             prop = 0;
85             do {
86                 prop = contextInterface->enumProps(context, prop);
87                 if (prop && (!omitHashes || !IS_CERT_HASH_PROP_ID(prop)))
88                 {
89                     DWORD propSize = 0;
90
91                     ret = contextInterface->getProp(context, prop, NULL,
92                      &propSize);
93                     if (ret)
94                     {
95                         if (bufSize < propSize)
96                         {
97                             if (buf)
98                                 buf = CryptMemRealloc(buf, propSize);
99                             else
100                                 buf = CryptMemAlloc(propSize);
101                             bufSize = propSize;
102                         }
103                         if (buf)
104                         {
105                             ret = contextInterface->getProp(context, prop, buf,
106                              &propSize);
107                             if (ret)
108                             {
109                                 hdr = (PWINE_CERT_PROP_HEADER)pbElement;
110                                 hdr->propID = prop;
111                                 hdr->unknown = 1;
112                                 hdr->cb = propSize;
113                                 pbElement += sizeof(WINE_CERT_PROP_HEADER);
114                                 if (propSize)
115                                 {
116                                     memcpy(pbElement, buf, propSize);
117                                     pbElement += propSize;
118                                 }
119                             }
120                         }
121                         else
122                             ret = FALSE;
123                     }
124                 }
125             } while (ret && prop != 0);
126             CryptMemFree(buf);
127
128             hdr = (PWINE_CERT_PROP_HEADER)pbElement;
129             hdr->propID = contextPropID;
130             hdr->unknown = 1;
131             hdr->cb = cbEncodedContext;
132             memcpy(pbElement + sizeof(WINE_CERT_PROP_HEADER),
133              encodedContext, cbEncodedContext);
134         }
135     }
136     else
137         ret = FALSE;
138     return ret;
139 }
140
141 BOOL WINAPI CertSerializeCertificateStoreElement(PCCERT_CONTEXT pCertContext,
142  DWORD dwFlags, BYTE *pbElement, DWORD *pcbElement)
143 {
144     return CRYPT_SerializeStoreElement(pCertContext,
145      pCertContext->pbCertEncoded, pCertContext->cbCertEncoded,
146      CERT_CERT_PROP_ID, pCertInterface, dwFlags, FALSE, pbElement, pcbElement);
147 }
148
149 BOOL WINAPI CertSerializeCRLStoreElement(PCCRL_CONTEXT pCrlContext,
150  DWORD dwFlags, BYTE *pbElement, DWORD *pcbElement)
151 {
152     return CRYPT_SerializeStoreElement(pCrlContext,
153      pCrlContext->pbCrlEncoded, pCrlContext->cbCrlEncoded,
154      CERT_CRL_PROP_ID, pCRLInterface, dwFlags, FALSE, pbElement, pcbElement);
155 }
156
157 BOOL WINAPI CertSerializeCTLStoreElement(PCCTL_CONTEXT pCtlContext,
158  DWORD dwFlags, BYTE *pbElement, DWORD *pcbElement)
159 {
160     return CRYPT_SerializeStoreElement(pCtlContext,
161      pCtlContext->pbCtlEncoded, pCtlContext->cbCtlEncoded,
162      CERT_CTL_PROP_ID, pCTLInterface, dwFlags, FALSE, pbElement, pcbElement);
163 }
164
165 /* Looks for the property with ID propID in the buffer buf.  Returns a pointer
166  * to its header if a valid header is found, NULL if not.  Valid means the
167  * length of thte property won't overrun buf, and the unknown field is 1.
168  */
169 static const WINE_CERT_PROP_HEADER *CRYPT_findPropID(const BYTE *buf,
170  DWORD size, DWORD propID)
171 {
172     const WINE_CERT_PROP_HEADER *ret = NULL;
173     BOOL done = FALSE;
174
175     while (size && !ret && !done)
176     {
177         if (size < sizeof(WINE_CERT_PROP_HEADER))
178         {
179             SetLastError(CRYPT_E_FILE_ERROR);
180             done = TRUE;
181         }
182         else
183         {
184             const WINE_CERT_PROP_HEADER *hdr =
185              (const WINE_CERT_PROP_HEADER *)buf;
186
187             size -= sizeof(WINE_CERT_PROP_HEADER);
188             buf += sizeof(WINE_CERT_PROP_HEADER);
189             if (size < hdr->cb)
190             {
191                 SetLastError(E_INVALIDARG);
192                 done = TRUE;
193             }
194             else if (!hdr->propID)
195             {
196                 /* assume a zero prop ID means the data are uninitialized, so
197                  * stop looking.
198                  */
199                 done = TRUE;
200             }
201             else if (hdr->unknown != 1)
202             {
203                 SetLastError(ERROR_FILE_NOT_FOUND);
204                 done = TRUE;
205             }
206             else if (hdr->propID == propID)
207                 ret = hdr;
208             else
209             {
210                 buf += hdr->cb;
211                 size -= hdr->cb;
212             }
213         }
214     }
215     return ret;
216 }
217
218 static BOOL CRYPT_ReadContextProp(
219  const WINE_CONTEXT_INTERFACE *contextInterface, const void *context,
220  const WINE_CERT_PROP_HEADER *hdr, const BYTE *pbElement, DWORD cbElement)
221 {
222     BOOL ret;
223
224     if (cbElement < hdr->cb)
225     {
226         SetLastError(E_INVALIDARG);
227         ret = FALSE;
228     }
229     else if (hdr->unknown != 1)
230     {
231         SetLastError(ERROR_FILE_NOT_FOUND);
232         ret = FALSE;
233     }
234     else if (hdr->propID != CERT_CERT_PROP_ID &&
235      hdr->propID != CERT_CRL_PROP_ID && hdr->propID != CERT_CTL_PROP_ID)
236     {
237         /* Have to create a blob for most types, but not
238          * for all.. arghh.
239          */
240         switch (hdr->propID)
241         {
242         case CERT_AUTO_ENROLL_PROP_ID:
243         case CERT_CTL_USAGE_PROP_ID:
244         case CERT_DESCRIPTION_PROP_ID:
245         case CERT_FRIENDLY_NAME_PROP_ID:
246         case CERT_HASH_PROP_ID:
247         case CERT_KEY_IDENTIFIER_PROP_ID:
248         case CERT_MD5_HASH_PROP_ID:
249         case CERT_NEXT_UPDATE_LOCATION_PROP_ID:
250         case CERT_PUBKEY_ALG_PARA_PROP_ID:
251         case CERT_PVK_FILE_PROP_ID:
252         case CERT_SIGNATURE_HASH_PROP_ID:
253         case CERT_ISSUER_PUBLIC_KEY_MD5_HASH_PROP_ID:
254         case CERT_SUBJECT_PUBLIC_KEY_MD5_HASH_PROP_ID:
255         case CERT_ENROLLMENT_PROP_ID:
256         case CERT_CROSS_CERT_DIST_POINTS_PROP_ID:
257         case CERT_RENEWAL_PROP_ID:
258         {
259             CRYPT_DATA_BLOB blob = { hdr->cb,
260              (LPBYTE)pbElement };
261
262             ret = contextInterface->setProp(context,
263              hdr->propID, 0, &blob);
264             break;
265         }
266         case CERT_DATE_STAMP_PROP_ID:
267             ret = contextInterface->setProp(context,
268              hdr->propID, 0, pbElement);
269             break;
270         case CERT_KEY_PROV_INFO_PROP_ID:
271         {
272             PCRYPT_KEY_PROV_INFO info =
273              (PCRYPT_KEY_PROV_INFO)pbElement;
274
275             CRYPT_FixKeyProvInfoPointers(info);
276             ret = contextInterface->setProp(context,
277              hdr->propID, 0, pbElement);
278             break;
279         }
280         default:
281             ret = FALSE;
282         }
283     }
284     else
285     {
286         /* ignore the context itself */
287         ret = TRUE;
288     }
289     return ret;
290 }
291
292 const void *CRYPT_ReadSerializedElement(const BYTE *pbElement, DWORD cbElement,
293  DWORD dwContextTypeFlags, DWORD *pdwContentType)
294 {
295     const void *context;
296
297     TRACE("(%p, %d, %08x, %p)\n", pbElement, cbElement, dwContextTypeFlags,
298      pdwContentType);
299
300     if (!cbElement)
301     {
302         SetLastError(ERROR_END_OF_MEDIA);
303         return NULL;
304     }
305
306     __TRY
307     {
308         const WINE_CONTEXT_INTERFACE *contextInterface = NULL;
309         const WINE_CERT_PROP_HEADER *hdr = NULL;
310         DWORD type = 0;
311         BOOL ret;
312
313         ret = TRUE;
314         context = NULL;
315         if (dwContextTypeFlags == CERT_STORE_ALL_CONTEXT_FLAG)
316         {
317             hdr = CRYPT_findPropID(pbElement, cbElement, CERT_CERT_PROP_ID);
318             if (hdr)
319                 type = CERT_STORE_CERTIFICATE_CONTEXT;
320             else
321             {
322                 hdr = CRYPT_findPropID(pbElement, cbElement, CERT_CRL_PROP_ID);
323                 if (hdr)
324                     type = CERT_STORE_CRL_CONTEXT;
325                 else
326                 {
327                     hdr = CRYPT_findPropID(pbElement, cbElement,
328                      CERT_CTL_PROP_ID);
329                     if (hdr)
330                         type = CERT_STORE_CTL_CONTEXT;
331                 }
332             }
333         }
334         else if (dwContextTypeFlags & CERT_STORE_CERTIFICATE_CONTEXT_FLAG)
335         {
336             hdr = CRYPT_findPropID(pbElement, cbElement, CERT_CERT_PROP_ID);
337             type = CERT_STORE_CERTIFICATE_CONTEXT;
338         }
339         else if (dwContextTypeFlags & CERT_STORE_CRL_CONTEXT_FLAG)
340         {
341             hdr = CRYPT_findPropID(pbElement, cbElement, CERT_CRL_PROP_ID);
342             type = CERT_STORE_CRL_CONTEXT;
343         }
344         else if (dwContextTypeFlags & CERT_STORE_CTL_CONTEXT_FLAG)
345         {
346             hdr = CRYPT_findPropID(pbElement, cbElement, CERT_CTL_PROP_ID);
347             type = CERT_STORE_CTL_CONTEXT;
348         }
349
350         switch (type)
351         {
352         case CERT_STORE_CERTIFICATE_CONTEXT:
353             contextInterface = pCertInterface;
354             break;
355         case CERT_STORE_CRL_CONTEXT:
356             contextInterface = pCRLInterface;
357             break;
358         case CERT_STORE_CTL_CONTEXT:
359             contextInterface = pCTLInterface;
360             break;
361         default:
362             SetLastError(E_INVALIDARG);
363             ret = FALSE;
364         }
365         if (!hdr)
366             ret = FALSE;
367
368         if (ret)
369             context = contextInterface->create(X509_ASN_ENCODING,
370              (BYTE *)hdr + sizeof(WINE_CERT_PROP_HEADER), hdr->cb);
371         if (ret && context)
372         {
373             BOOL noMoreProps = FALSE;
374
375             while (!noMoreProps && ret)
376             {
377                 if (cbElement < sizeof(WINE_CERT_PROP_HEADER))
378                     ret = FALSE;
379                 else
380                 {
381                     const WINE_CERT_PROP_HEADER *hdr =
382                      (const WINE_CERT_PROP_HEADER *)pbElement;
383
384                     TRACE("prop is %d\n", hdr->propID);
385                     cbElement -= sizeof(WINE_CERT_PROP_HEADER);
386                     pbElement += sizeof(WINE_CERT_PROP_HEADER);
387                     if (!hdr->propID)
388                     {
389                         /* Like in CRYPT_findPropID, stop if the propID is zero
390                          */
391                         noMoreProps = TRUE;
392                     }
393                     else
394                         ret = CRYPT_ReadContextProp(contextInterface, context,
395                          hdr, pbElement, cbElement);
396                     pbElement += hdr->cb;
397                     cbElement -= hdr->cb;
398                     if (!cbElement)
399                         noMoreProps = TRUE;
400                 }
401             }
402             if (ret)
403             {
404                 if (pdwContentType)
405                     *pdwContentType = type;
406             }
407             else
408             {
409                 contextInterface->free(context);
410                 context = NULL;
411             }
412         }
413     }
414     __EXCEPT_PAGE_FAULT
415     {
416         SetLastError(STATUS_ACCESS_VIOLATION);
417         context = NULL;
418     }
419     __ENDTRY
420     return context;
421 }
422
423 static const BYTE fileHeader[] = { 0, 0, 0, 0, 'C','E','R','T' };
424
425 BOOL CRYPT_ReadSerializedFile(HANDLE file, HCERTSTORE store)
426 {
427     BYTE fileHeaderBuf[sizeof(fileHeader)];
428     DWORD read;
429     BOOL ret;
430
431     /* Failure reading is non-critical, we'll leave the store empty */
432     ret = ReadFile(file, fileHeaderBuf, sizeof(fileHeaderBuf), &read, NULL);
433     if (ret)
434     {
435         if (!memcmp(fileHeaderBuf, fileHeader, read))
436         {
437             WINE_CERT_PROP_HEADER propHdr;
438             const void *context = NULL;
439             const WINE_CONTEXT_INTERFACE *contextInterface = NULL;
440             LPBYTE buf = NULL;
441             DWORD bufSize = 0;
442
443             do {
444                 ret = ReadFile(file, &propHdr, sizeof(propHdr), &read, NULL);
445                 if (ret && read == sizeof(propHdr))
446                 {
447                     if (contextInterface && context &&
448                      (propHdr.propID == CERT_CERT_PROP_ID ||
449                      propHdr.propID == CERT_CRL_PROP_ID ||
450                      propHdr.propID == CERT_CTL_PROP_ID))
451                     {
452                         /* We have a new context, so free the existing one */
453                         contextInterface->free(context);
454                     }
455                     if (propHdr.cb > bufSize)
456                     {
457                         /* Not reusing realloc, because the old data aren't
458                          * needed any longer.
459                          */
460                         CryptMemFree(buf);
461                         buf = CryptMemAlloc(propHdr.cb);
462                         bufSize = propHdr.cb;
463                     }
464                     if (buf)
465                     {
466                         ret = ReadFile(file, buf, propHdr.cb, &read, NULL);
467                         if (ret && read == propHdr.cb)
468                         {
469                             if (propHdr.propID == CERT_CERT_PROP_ID)
470                             {
471                                 contextInterface = pCertInterface;
472                                 ret = contextInterface->addEncodedToStore(store,
473                                  X509_ASN_ENCODING, buf, read,
474                                  CERT_STORE_ADD_NEW, &context);
475                             }
476                             else if (propHdr.propID == CERT_CRL_PROP_ID)
477                             {
478                                 contextInterface = pCRLInterface;
479                                 ret = contextInterface->addEncodedToStore(store,
480                                  X509_ASN_ENCODING, buf, read,
481                                  CERT_STORE_ADD_NEW, &context);
482                             }
483                             else if (propHdr.propID == CERT_CTL_PROP_ID)
484                             {
485                                 contextInterface = pCTLInterface;
486                                 ret = contextInterface->addEncodedToStore(store,
487                                  X509_ASN_ENCODING, buf, read,
488                                  CERT_STORE_ADD_NEW, &context);
489                             }
490                             else
491                                 ret = CRYPT_ReadContextProp(contextInterface,
492                                  context, &propHdr, buf, read);
493                         }
494                     }
495                     else
496                         ret = FALSE;
497                 }
498             } while (ret && read > 0);
499             if (contextInterface && context)
500             {
501                 /* Free the last context added */
502                 contextInterface->free(context);
503             }
504             CryptMemFree(buf);
505             ret = TRUE;
506         }
507     }
508     else
509         ret = TRUE;
510     return ret;
511 }
512
513 static BOOL WINAPI CRYPT_SerializeCertNoHash(PCCERT_CONTEXT pCertContext,
514  DWORD dwFlags, BYTE *pbElement, DWORD *pcbElement)
515 {
516     return CRYPT_SerializeStoreElement(pCertContext,
517      pCertContext->pbCertEncoded, pCertContext->cbCertEncoded,
518      CERT_CERT_PROP_ID, pCertInterface, dwFlags, TRUE, pbElement, pcbElement);
519 }
520
521 static BOOL WINAPI CRYPT_SerializeCRLNoHash(PCCRL_CONTEXT pCrlContext,
522  DWORD dwFlags, BYTE *pbElement, DWORD *pcbElement)
523 {
524     return CRYPT_SerializeStoreElement(pCrlContext,
525      pCrlContext->pbCrlEncoded, pCrlContext->cbCrlEncoded,
526      CERT_CRL_PROP_ID, pCRLInterface, dwFlags, TRUE, pbElement, pcbElement);
527 }
528
529 static BOOL WINAPI CRYPT_SerializeCTLNoHash(PCCTL_CONTEXT pCtlContext,
530  DWORD dwFlags, BYTE *pbElement, DWORD *pcbElement)
531 {
532     return CRYPT_SerializeStoreElement(pCtlContext,
533      pCtlContext->pbCtlEncoded, pCtlContext->cbCtlEncoded,
534      CERT_CTL_PROP_ID, pCTLInterface, dwFlags, TRUE, pbElement, pcbElement);
535 }
536
537 static BOOL CRYPT_SerializeContextsToFile(HANDLE file,
538  const WINE_CONTEXT_INTERFACE *contextInterface, HCERTSTORE store)
539 {
540     const void *context = NULL;
541     BOOL ret;
542
543     do {
544         context = contextInterface->enumContextsInStore(store, context);
545         if (context)
546         {
547             DWORD size = 0;
548             LPBYTE buf = NULL;
549
550             ret = contextInterface->serialize(context, 0, NULL, &size);
551             if (size)
552                 buf = CryptMemAlloc(size);
553             if (buf)
554             {
555                 ret = contextInterface->serialize(context, 0, buf, &size);
556                 if (ret)
557                     ret = WriteFile(file, buf, size, &size, NULL);
558             }
559             CryptMemFree(buf);
560         }
561         else
562             ret = TRUE;
563     } while (ret && context != NULL);
564     if (context)
565         contextInterface->free(context);
566     return ret;
567 }
568
569 BOOL CRYPT_WriteSerializedFile(HANDLE file, HCERTSTORE store)
570 {
571     static const BYTE fileTrailer[12] = { 0 };
572     WINE_CONTEXT_INTERFACE interface;
573     BOOL ret;
574     DWORD size;
575
576     SetFilePointer(file, 0, NULL, FILE_BEGIN);
577     ret = WriteFile(file, fileHeader, sizeof(fileHeader), &size, NULL);
578     if (ret)
579     {
580         memcpy(&interface, pCertInterface, sizeof(interface));
581         interface.serialize = (SerializeElementFunc)CRYPT_SerializeCertNoHash;
582         ret = CRYPT_SerializeContextsToFile(file, &interface, store);
583     }
584     if (ret)
585     {
586         memcpy(&interface, pCRLInterface, sizeof(interface));
587         interface.serialize = (SerializeElementFunc)CRYPT_SerializeCRLNoHash;
588         ret = CRYPT_SerializeContextsToFile(file, &interface, store);
589     }
590     if (ret)
591     {
592         memcpy(&interface, pCTLInterface, sizeof(interface));
593         interface.serialize = (SerializeElementFunc)CRYPT_SerializeCTLNoHash;
594         ret = CRYPT_SerializeContextsToFile(file, &interface, store);
595     }
596     if (ret)
597         ret = WriteFile(file, fileTrailer, sizeof(fileTrailer), &size, NULL);
598     return ret;
599 }
600
601 BOOL WINAPI CertAddSerializedElementToStore(HCERTSTORE hCertStore,
602  const BYTE *pbElement, DWORD cbElement, DWORD dwAddDisposition, DWORD dwFlags,
603  DWORD dwContextTypeFlags, DWORD *pdwContentType, const void **ppvContext)
604 {
605     const void *context;
606     DWORD type;
607     BOOL ret;
608
609     TRACE("(%p, %p, %d, %08x, %08x, %08x, %p, %p)\n", hCertStore,
610      pbElement, cbElement, dwAddDisposition, dwFlags, dwContextTypeFlags,
611      pdwContentType, ppvContext);
612
613     /* Call the internal function, then delete the hashes.  Tests show this
614      * function uses real hash values, not whatever's stored in the hash
615      * property.
616      */
617     context = CRYPT_ReadSerializedElement(pbElement, cbElement,
618      dwContextTypeFlags, &type);
619     if (context)
620     {
621         const WINE_CONTEXT_INTERFACE *contextInterface = NULL;
622
623         switch (type)
624         {
625         case CERT_STORE_CERTIFICATE_CONTEXT:
626             contextInterface = pCertInterface;
627             break;
628         case CERT_STORE_CRL_CONTEXT:
629             contextInterface = pCRLInterface;
630             break;
631         case CERT_STORE_CTL_CONTEXT:
632             contextInterface = pCTLInterface;
633             break;
634         default:
635             SetLastError(E_INVALIDARG);
636         }
637         if (contextInterface)
638         {
639             contextInterface->setProp(context, CERT_HASH_PROP_ID, 0, NULL);
640             contextInterface->setProp(context, CERT_MD5_HASH_PROP_ID, 0, NULL);
641             contextInterface->setProp(context, CERT_SIGNATURE_HASH_PROP_ID, 0,
642              NULL);
643             if (pdwContentType)
644                 *pdwContentType = type;
645             ret = contextInterface->addContextToStore(hCertStore, context,
646              dwAddDisposition, ppvContext);
647             contextInterface->free(context);
648         }
649         else
650             ret = FALSE;
651     }
652     else
653         ret = FALSE;
654     return ret;
655 }