1 #include "stddef.h"
2
3 #include <libcr51sign/cr51_image_descriptor.h>
4 #include <libcr51sign/libcr51sign.h>
5 #include <libcr51sign/libcr51sign_internal.h>
6 #include <libcr51sign/libcr51sign_mauv.h>
7 #include <stdint.h>
8
9 #ifdef __cplusplus
10 extern "C"
11 {
12 #endif
13
14 #define IMAGE_MAUV_MAX_DENYLIST_ENTRIES \
15 ((IMAGE_MAUV_DATA_MAX_SIZE - sizeof(struct image_mauv)) / sizeof(uint64_t))
16
17 _Static_assert(
18 (sizeof(struct image_mauv) +
19 IMAGE_MAUV_MAX_DENYLIST_ENTRIES *
20 MEMBER_SIZE(struct image_mauv, version_denylist[0])) ==
21 IMAGE_MAUV_DATA_MAX_SIZE,
22 "IMAGE_MAUV_MAX_DENYLIST_ENTRIES number of denylist entries do not "
23 "completely fill IMAGE_MAUV_MAX_SIZE bytes assumed for data in struct "
24 "image_mauv");
25
26 // Use wrapper struct to preserve alignment of image_mauv
27 struct full_mauv
28 {
29 struct image_mauv mauv;
30 uint8_t extra[IMAGE_MAUV_DATA_MAX_SIZE - sizeof(struct image_mauv)];
31 };
32
33 // Verify BLOB magic bytes in payload's image descriptor at the expected offset
34 //
35 // @param[in] ctx context which describes the image and holds opaque private
36 // data for the user of the library
37 // @param[in] intf function pointers which interface to the current system
38 // and environment
39 // @param[in] payload_blob_offset Absolute offset of payload BLOB data in
40 // payload's image descriptor
41 //
42 // @return `failure_reason`
43 static failure_reason
verify_payload_blob_magic(const struct libcr51sign_ctx * const ctx,const struct libcr51sign_intf * const intf,const uint32_t payload_blob_offset)44 verify_payload_blob_magic(const struct libcr51sign_ctx* const ctx,
45 const struct libcr51sign_intf* const intf,
46 const uint32_t payload_blob_offset)
47 {
48 int irv = 0;
49 struct blob payload_blob = {0};
50
51 if (!intf->read)
52 {
53 CPRINTS(ctx, "%s: Missing interface intf->read\n", __FUNCTION__);
54 return LIBCR51SIGN_ERROR_INVALID_INTERFACE;
55 }
56
57 irv = intf->read(ctx, payload_blob_offset, sizeof(struct blob),
58 (uint8_t*)&payload_blob);
59 if (irv != LIBCR51SIGN_SUCCESS)
60 {
61 CPRINTS(ctx, "%s: Could not read BLOB magic bytes from payload\n",
62 __FUNCTION__);
63 return LIBCR51SIGN_ERROR_RUNTIME_FAILURE;
64 }
65
66 if (payload_blob.blob_magic != BLOB_MAGIC)
67 {
68 CPRINTS(ctx, "%s: BLOB magic bytes read from payload(%x) are invalid\n",
69 __FUNCTION__, payload_blob.blob_magic);
70 return LIBCR51SIGN_ERROR_INVALID_DESCRIPTOR;
71 }
72
73 return LIBCR51SIGN_SUCCESS;
74 }
75
76 // Find offset of Image MAUV data in payload BLOB inside the image descriptor
77 //
78 // @param[in] ctx context which describes the image and holds opaque private
79 // data for the user of the library
80 // @param[in] intf function pointers which interface to the current system
81 // and environment
82 // @param[in] offset_after_payload_blob_magic Absolute offset of data after
83 // payload BLOB magic bytes in image
84 // descriptor
85 // @param[in] payload_blob_size Size of payload BLOB as per its image
86 // descriptor
87 // @param[out] payload_image_mauv_data_offset Absolute offset of Image MAUV
88 // data in payload's image
89 // descriptor
90 // @param[out] payload_image_mauv_data_size Size of Image MAUV data embedded in
91 // payload's image descriptor
92 //
93 // @return `failure_reason`
find_image_mauv_data_offset_in_payload(const struct libcr51sign_ctx * const ctx,const struct libcr51sign_intf * const intf,const uint32_t offset_after_payload_blob_magic,const uint32_t payload_blob_size,uint32_t * const restrict payload_image_mauv_data_offset,uint32_t * const restrict payload_image_mauv_data_size)94 static failure_reason find_image_mauv_data_offset_in_payload(
95 const struct libcr51sign_ctx* const ctx,
96 const struct libcr51sign_intf* const intf,
97 const uint32_t offset_after_payload_blob_magic,
98 const uint32_t payload_blob_size,
99 uint32_t* const restrict payload_image_mauv_data_offset,
100 uint32_t* const restrict payload_image_mauv_data_size)
101 {
102 struct blob_data payload_blob_data = {0};
103 int irv = 0;
104 bool found_image_mauv_data = false;
105
106 if (!intf->read)
107 {
108 CPRINTS(ctx, "%s: Missing interface intf->read\n", __FUNCTION__);
109 return LIBCR51SIGN_ERROR_INVALID_INTERFACE;
110 }
111 for (uint32_t current_offset = offset_after_payload_blob_magic;
112 current_offset <= offset_after_payload_blob_magic + payload_blob_size -
113 sizeof(struct blob_data);
114 /* increment based on each blob_data's size in loop */)
115 {
116 irv = intf->read(ctx, current_offset, sizeof(struct blob_data),
117 (uint8_t*)&payload_blob_data);
118 if (irv != LIBCR51SIGN_SUCCESS)
119 {
120 CPRINTS(ctx, "%s: Could not read BLOB data at offset %x\n",
121 __FUNCTION__, current_offset);
122 return LIBCR51SIGN_ERROR_RUNTIME_FAILURE;
123 }
124
125 if ((current_offset - offset_after_payload_blob_magic) +
126 sizeof(struct blob_data) + payload_blob_data.blob_payload_size >
127 payload_blob_size)
128 {
129 CPRINTS(
130 ctx,
131 "%s: BLOB payload size crosses threshold expected by blob_size "
132 "in image descriptor",
133 __FUNCTION__);
134 return LIBCR51SIGN_ERROR_INVALID_DESCRIPTOR;
135 }
136
137 switch (payload_blob_data.blob_type_magic)
138 {
139 case BLOB_TYPE_MAGIC_MAUV:
140 if (!found_image_mauv_data)
141 {
142 *payload_image_mauv_data_offset =
143 current_offset + sizeof(struct blob_data);
144 *payload_image_mauv_data_size =
145 payload_blob_data.blob_payload_size;
146 found_image_mauv_data = true;
147 /* intentional fall-through to increment current offset */
148 }
149 else
150 {
151 /* There should be only one Image MAUV in a valid image
152 * descriptor */
153 CPRINTS(
154 ctx,
155 "%s: Found multiple Image MAUV BLOB instances in payload\n",
156 __FUNCTION__);
157 return LIBCR51SIGN_ERROR_INVALID_DESCRIPTOR;
158 }
159 default:
160 current_offset += sizeof(struct blob_data) +
161 payload_blob_data.blob_payload_size;
162 /* Increment offset to keep the expected alignment */
163 current_offset =
164 ((current_offset - 1) & ~(BLOB_DATA_ALIGNMENT - 1)) +
165 BLOB_DATA_ALIGNMENT;
166 break;
167 }
168 }
169 if (!found_image_mauv_data)
170 {
171 CPRINTS(ctx, "%s: Did not find Image MAUV BLOB inside payload\n",
172 __FUNCTION__);
173 }
174 return LIBCR51SIGN_SUCCESS;
175 }
176
177 // Read Image MAUV data from BLOB inside payload's image descriptor
178 //
179 // @param[in] ctx context which describes the image and holds opaque private
180 // data for the user of the library
181 // @param[in] intf function pointers which interface to the current system
182 // and environment
183 // @param[in] payload_image_mauv_data_offset Absolute offset of Image MAUV data
184 // in payload's image descriptor
185 // @param[in] payload_image_mauv_data_size Size of Image MAUV data embedded in
186 // payload's image descriptor
187 // @param[out] payload_image_mauv_data_buffer Buffer to store Image MAUV data
188 // read from payload's image
189 // descriptor
190 //
191 // @return `failure_reason`
read_image_mauv_data_from_payload(const struct libcr51sign_ctx * const ctx,const struct libcr51sign_intf * const intf,const uint32_t payload_image_mauv_data_offset,const uint32_t payload_image_mauv_data_size,struct image_mauv * const restrict payload_image_mauv_data_buffer)192 static failure_reason read_image_mauv_data_from_payload(
193 const struct libcr51sign_ctx* const ctx,
194 const struct libcr51sign_intf* const intf,
195 const uint32_t payload_image_mauv_data_offset,
196 const uint32_t payload_image_mauv_data_size,
197 struct image_mauv* const restrict payload_image_mauv_data_buffer)
198 {
199 int irv = 0;
200
201 if (!intf->read)
202 {
203 CPRINTS(ctx, "%s: Missing interface intf->read\n", __FUNCTION__);
204 return LIBCR51SIGN_ERROR_INVALID_INTERFACE;
205 }
206
207 if (payload_image_mauv_data_size > IMAGE_MAUV_DATA_MAX_SIZE)
208 {
209 CPRINTS(
210 ctx,
211 "%s: Payload Image MAUV data size (0x%x) is more than supported "
212 "maximum size\n",
213 __FUNCTION__, payload_image_mauv_data_size);
214 return LIBCR51SIGN_ERROR_INVALID_IMAGE_MAUV_DATA;
215 }
216
217 irv = intf->read(ctx, payload_image_mauv_data_offset,
218 payload_image_mauv_data_size,
219 (uint8_t*)payload_image_mauv_data_buffer);
220 if (irv != LIBCR51SIGN_SUCCESS)
221 {
222 CPRINTS(ctx,
223 "%s: Could not read Image MAUV data from payload @ offset 0x%x "
224 "size 0x%x\n",
225 __FUNCTION__, payload_image_mauv_data_offset,
226 payload_image_mauv_data_size);
227 return LIBCR51SIGN_ERROR_RUNTIME_FAILURE;
228 }
229
230 return LIBCR51SIGN_SUCCESS;
231 }
232
233 // Check if Image MAUV allows update to a target payload version
234 //
235 // @param[in] stored_image_mauv_data Image MAUV data stored in system
236 // @param[in] new_payload_security_version Payload version
237 //
238 // @return `bool` `True` if update to target version is allowed by MAUV data
does_stored_image_mauv_allow_update(const struct image_mauv * const stored_image_mauv_data,const uint64_t new_payload_security_version)239 static bool does_stored_image_mauv_allow_update(
240 const struct image_mauv* const stored_image_mauv_data,
241 const uint64_t new_payload_security_version)
242 {
243 if (new_payload_security_version <
244 stored_image_mauv_data->minimum_acceptable_update_version)
245 {
246 return false;
247 }
248
249 for (uint32_t i = 0;
250 i < stored_image_mauv_data->version_denylist_num_entries; i++)
251 {
252 if (stored_image_mauv_data->version_denylist[i] ==
253 new_payload_security_version)
254 {
255 return false;
256 }
257 }
258
259 return true;
260 }
261
262 // Do a sanity check for values stored in Image MAUV data
263 //
264 // @param[in] image_mauv_data Image MAUV data
265 // @param[in] image_mauv_data_size Size of Image MAUV data in bytes
266 //
267 // @return `failure_reason`
sanity_check_image_mauv_data(const struct image_mauv * const restrict image_mauv_data,const uint32_t image_mauv_data_size)268 static failure_reason sanity_check_image_mauv_data(
269 const struct image_mauv* const restrict image_mauv_data,
270 const uint32_t image_mauv_data_size)
271 {
272 uint32_t expected_image_mauv_data_size = 0;
273
274 if (image_mauv_data_size < sizeof(struct image_mauv))
275 {
276 CPRINTS(ctx, "%s: Image MAUV data size is smaller than expected\n",
277 __FUNCTION__);
278 return LIBCR51SIGN_ERROR_INVALID_IMAGE_MAUV_DATA;
279 }
280
281 if (image_mauv_data->mauv_struct_version != IMAGE_MAUV_STRUCT_VERSION)
282 {
283 CPRINTS(ctx, "%s: Unexpected Image MAUV version\n", __FUNCTION__);
284 return LIBCR51SIGN_ERROR_INVALID_IMAGE_MAUV_DATA;
285 }
286
287 if (image_mauv_data->payload_security_version == 0)
288 {
289 // Handle trivial case of someone not initializing MAUV properly
290 CPRINTS(ctx, "%s: Payload security version should be greater than 0\n",
291 __FUNCTION__);
292 return LIBCR51SIGN_ERROR_INVALID_IMAGE_MAUV_DATA;
293 }
294
295 if (image_mauv_data->version_denylist_num_entries >
296 IMAGE_MAUV_MAX_DENYLIST_ENTRIES)
297 {
298 CPRINTS(
299 ctx,
300 "%s: Version denylist entries in Image MAUV exceed maximum count\n",
301 __FUNCTION__);
302 return LIBCR51SIGN_ERROR_INVALID_IMAGE_MAUV_DATA;
303 }
304
305 expected_image_mauv_data_size =
306 sizeof(struct image_mauv) +
307 image_mauv_data->version_denylist_num_entries *
308 MEMBER_SIZE(struct image_mauv, version_denylist[0]);
309
310 if (image_mauv_data_size != expected_image_mauv_data_size)
311 {
312 CPRINTS(ctx,
313 "%s: Size of Image MAUV data (0x%x) is different than expected "
314 "size (0x%x)\n",
315 __FUNCTION__, image_mauv_data_size,
316 expected_image_mauv_data_size);
317 return LIBCR51SIGN_ERROR_INVALID_IMAGE_MAUV_DATA;
318 }
319
320 if (!does_stored_image_mauv_allow_update(
321 image_mauv_data, image_mauv_data->payload_security_version))
322 {
323 CPRINTS(ctx,
324 "%s: Image MAUV does not allow update to the payload it was "
325 "contained in\n",
326 __FUNCTION__);
327 return LIBCR51SIGN_ERROR_INVALID_IMAGE_MAUV_DATA;
328 }
329 return LIBCR51SIGN_SUCCESS;
330 }
331
332 // Find and read (if found) Image MAUV from payload's image descriptor
333 //
334 // @param[in] ctx context which describes the image and holds opaque private
335 // data for the user of the library
336 // @param[in] intf function pointers which interface to the current system
337 // and environment
338 // @param[in] payload_blob_offset Absolute offset of payload BLOB data in
339 // payload's image descriptor
340 // @param[in] payload_blob_size Size of payload BLOB data as per payload's
341 // image descriptor
342 // @param[out] payload_image_mauv_data_buffer Buffer to store Image MAUV data
343 // read from payload's image
344 // descriptor
345 // @param[out] payload_image_mauv_data_size Size of Image MAUV data (in bytes)
346 // read from payload's image
347 // descriptor
348 // @param[out] payload_contains_image_mauv_data Flag to indicate whether Image
349 // MAUV data is present in
350 // payload's image descriptor
351 //
352 // @return `failure_reason`
find_and_read_image_mauv_data_from_payload(const struct libcr51sign_ctx * const ctx,const struct libcr51sign_intf * const intf,const uint32_t payload_blob_offset,const uint32_t payload_blob_size,uint8_t payload_image_mauv_data_buffer[],uint32_t * const restrict payload_image_mauv_data_size,bool * const restrict payload_contains_image_mauv_data)353 failure_reason find_and_read_image_mauv_data_from_payload(
354 const struct libcr51sign_ctx* const ctx,
355 const struct libcr51sign_intf* const intf,
356 const uint32_t payload_blob_offset, const uint32_t payload_blob_size,
357 uint8_t payload_image_mauv_data_buffer[],
358 uint32_t* const restrict payload_image_mauv_data_size,
359 bool* const restrict payload_contains_image_mauv_data)
360 {
361 failure_reason rv = LIBCR51SIGN_SUCCESS;
362 uint32_t payload_image_mauv_data_offset = 0;
363
364 rv = verify_payload_blob_magic(ctx, intf, payload_blob_offset);
365 if (rv != LIBCR51SIGN_SUCCESS)
366 {
367 return rv;
368 }
369
370 rv = find_image_mauv_data_offset_in_payload(
371 ctx, intf, payload_blob_offset + offsetof(struct blob, blobs),
372 payload_blob_size, &payload_image_mauv_data_offset,
373 payload_image_mauv_data_size);
374 if (rv != LIBCR51SIGN_SUCCESS)
375 {
376 return rv;
377 }
378
379 *payload_contains_image_mauv_data = (payload_image_mauv_data_offset != 0);
380
381 if (*payload_contains_image_mauv_data)
382 {
383 rv = read_image_mauv_data_from_payload(
384 ctx, intf, payload_image_mauv_data_offset,
385 *payload_image_mauv_data_size,
386 (struct image_mauv*)payload_image_mauv_data_buffer);
387 if (rv != LIBCR51SIGN_SUCCESS)
388 {
389 return rv;
390 }
391
392 return sanity_check_image_mauv_data(
393 (struct image_mauv*)payload_image_mauv_data_buffer,
394 *payload_image_mauv_data_size);
395 }
396 return LIBCR51SIGN_SUCCESS;
397 }
398
399 // Replace stored Image MAUV data with payload Image MAUV data
400 //
401 // @param[in] ctx context which describes the image and holds opaque private
402 // data for the user of the library
403 // @param[in] intf function pointers which interface to the current system
404 // and environment
405 // @param[in] payload_image_mauv_data Image MAUV data from payload
406 // @param[in] payload_image_mauv_data_size Size of Image MAUV data (in bytes)
407 // embedded inside payload
408 //
409 // @return `failure_reason`
update_stored_image_mauv_data(const struct libcr51sign_ctx * const ctx,const struct libcr51sign_intf * const intf,const struct image_mauv * const restrict payload_image_mauv_data,const uint32_t payload_image_mauv_data_size)410 static failure_reason update_stored_image_mauv_data(
411 const struct libcr51sign_ctx* const ctx,
412 const struct libcr51sign_intf* const intf,
413 const struct image_mauv* const restrict payload_image_mauv_data,
414 const uint32_t payload_image_mauv_data_size)
415 {
416 int irv = 0;
417
418 if (!intf->store_new_image_mauv_data)
419 {
420 CPRINTS(ctx, "%s: Missing interface intf->store_new_image_mauv_data\n",
421 __FUNCTION__);
422 return LIBCR51SIGN_ERROR_INVALID_INTERFACE;
423 }
424
425 irv = intf->store_new_image_mauv_data(
426 ctx, (uint8_t*)payload_image_mauv_data, payload_image_mauv_data_size);
427 if (irv != LIBCR51SIGN_SUCCESS)
428 {
429 CPRINTS(ctx,
430 "%s: Could not store new Image MAUV data from the payload\n",
431 __FUNCTION__);
432 return LIBCR51SIGN_ERROR_STORING_NEW_IMAGE_MAUV_DATA;
433 }
434 return LIBCR51SIGN_SUCCESS;
435 }
436
437 // Validates Image MAUV from payload against stored Image MAUV (if present)
438 //
439 // @param[in] ctx context which describes the image and holds opaque private
440 // data for the user of the library
441 // @param[in] intf function pointers which interface to the current system
442 // and environment
443 // @param[in] payload_blob_offset Absolute offset of BLOB data embedded in
444 // image descriptor. `0` if BLOB data is not
445 // present in image descriptor
446 // @param[in] payload_blob_size Size of BLOB data from `blob_size` field in
447 // image descriptor
448 //
449 // @return `failure_reason`
validate_payload_image_mauv(const struct libcr51sign_ctx * const ctx,const struct libcr51sign_intf * const intf,const uint32_t payload_blob_offset,const uint32_t payload_blob_size)450 failure_reason validate_payload_image_mauv(
451 const struct libcr51sign_ctx* const ctx,
452 const struct libcr51sign_intf* const intf,
453 const uint32_t payload_blob_offset, const uint32_t payload_blob_size)
454 {
455 uint32_t payload_image_mauv_data_size = 0;
456 struct full_mauv payload_image_mauv_data_buffer = {0};
457
458 uint32_t stored_image_mauv_data_size = 0;
459 struct full_mauv stored_image_mauv_data_buffer = {0};
460
461 bool payload_contains_image_mauv_data = false;
462
463 failure_reason rv = LIBCR51SIGN_SUCCESS;
464 int irv = 0;
465
466 bool payload_blob_present = (payload_blob_offset != 0);
467 if (payload_blob_present)
468 {
469 rv = find_and_read_image_mauv_data_from_payload(
470 ctx, intf, payload_blob_offset, payload_blob_size,
471 (uint8_t*)&payload_image_mauv_data_buffer,
472 &payload_image_mauv_data_size, &payload_contains_image_mauv_data);
473 if (rv != LIBCR51SIGN_SUCCESS)
474 {
475 return rv;
476 }
477 }
478
479 if (!intf->retrieve_stored_image_mauv_data)
480 {
481 if (payload_contains_image_mauv_data)
482 {
483 CPRINTS(
484 ctx,
485 "%s: Payload contains Image MAUV data but required interface "
486 "intf->retrieve_stored_image_mauv_data is missing\n",
487 __FUNCTION__);
488 return LIBCR51SIGN_ERROR_INVALID_INTERFACE;
489 }
490 CPRINTS(
491 ctx,
492 "%s: Payload does not contain Image MAUV data and interface "
493 "intf->retrieve_stored_image_mauv_data is missing. Skipping MAUV "
494 "check for backward compatibility.\n",
495 __FUNCTION__);
496 return LIBCR51SIGN_SUCCESS;
497 }
498
499 irv = intf->retrieve_stored_image_mauv_data(
500 ctx, (uint8_t*)&stored_image_mauv_data_buffer,
501 &stored_image_mauv_data_size, IMAGE_MAUV_DATA_MAX_SIZE);
502 if (irv == LIBCR51SIGN_NO_STORED_MAUV_FOUND)
503 {
504 CPRINTS(
505 ctx,
506 "%s: Stored Image MAUV data not present in the system. Skipping Image "
507 "MAUV check\n",
508 __FUNCTION__);
509 if (payload_contains_image_mauv_data)
510 {
511 return update_stored_image_mauv_data(
512 ctx, intf, (struct image_mauv*)&payload_image_mauv_data_buffer,
513 payload_image_mauv_data_size);
514 }
515 return LIBCR51SIGN_SUCCESS;
516 }
517 if (irv != LIBCR51SIGN_SUCCESS)
518 {
519 CPRINTS(ctx, "%s: Could not retrieve Image MAUV stored in system\n",
520 __FUNCTION__);
521 return LIBCR51SIGN_ERROR_RETRIEVING_STORED_IMAGE_MAUV_DATA;
522 }
523 if (stored_image_mauv_data_size > IMAGE_MAUV_DATA_MAX_SIZE)
524 {
525 CPRINTS(ctx,
526 "%s: Stored Image MAUV data size (0x%x) is more than supported "
527 "maximum size\n",
528 __FUNCTION__, stored_image_mauv_data_size);
529 return LIBCR51SIGN_ERROR_INVALID_IMAGE_MAUV_DATA;
530 }
531
532 rv = sanity_check_image_mauv_data(
533 (struct image_mauv*)&stored_image_mauv_data_buffer,
534 stored_image_mauv_data_size);
535 if (rv != LIBCR51SIGN_SUCCESS)
536 {
537 return rv;
538 }
539
540 if (!payload_contains_image_mauv_data)
541 {
542 CPRINTS(ctx, "%s: Image MAUV expected to be present in payload",
543 __FUNCTION__);
544 return LIBCR51SIGN_ERROR_STORED_IMAGE_MAUV_EXPECTS_PAYLOAD_IMAGE_MAUV;
545 }
546
547 if (!does_stored_image_mauv_allow_update(
548 (struct image_mauv*)&stored_image_mauv_data_buffer,
549 ((struct image_mauv*)&payload_image_mauv_data_buffer)
550 ->payload_security_version))
551 {
552 CPRINTS(ctx,
553 "%s: Stored Image MAUV data does not allow update to payload "
554 "version\n",
555 __FUNCTION__);
556 return LIBCR51SIGN_ERROR_STORED_IMAGE_MAUV_DOES_NOT_ALLOW_UPDATE_TO_PAYLOAD;
557 }
558
559 if (((struct image_mauv*)&payload_image_mauv_data_buffer)
560 ->mauv_update_timestamp >
561 ((struct image_mauv*)&stored_image_mauv_data_buffer)
562 ->mauv_update_timestamp)
563 {
564 return update_stored_image_mauv_data(
565 ctx, intf, (struct image_mauv*)&payload_image_mauv_data_buffer,
566 payload_image_mauv_data_size);
567 }
568 return LIBCR51SIGN_SUCCESS;
569 }
570
571 #ifdef __cplusplus
572 } // extern "C"
573 #endif
574