Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: TYBLE16_simple_data_logger TYBLE16_MP3_Air
chachapoly.c
00001 /** 00002 * \file chachapoly.c 00003 * 00004 * \brief ChaCha20-Poly1305 AEAD construction based on RFC 7539. 00005 * 00006 * Copyright (C) 2006-2016, ARM Limited, All Rights Reserved 00007 * SPDX-License-Identifier: Apache-2.0 00008 * 00009 * Licensed under the Apache License, Version 2.0 (the "License"); you may 00010 * not use this file except in compliance with the License. 00011 * You may obtain a copy of the License at 00012 * 00013 * http://www.apache.org/licenses/LICENSE-2.0 00014 * 00015 * Unless required by applicable law or agreed to in writing, software 00016 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 00017 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 00018 * See the License for the specific language governing permissions and 00019 * limitations under the License. 00020 * 00021 * This file is part of mbed TLS (https://tls.mbed.org) 00022 */ 00023 #if !defined(MBEDTLS_CONFIG_FILE) 00024 #include "mbedtls/config.h" 00025 #else 00026 #include MBEDTLS_CONFIG_FILE 00027 #endif 00028 00029 #if defined(MBEDTLS_CHACHAPOLY_C) 00030 00031 #include "mbedtls/chachapoly.h" 00032 #include "mbedtls/platform_util.h" 00033 00034 #include <string.h> 00035 00036 #if defined(MBEDTLS_SELF_TEST) 00037 #if defined(MBEDTLS_PLATFORM_C) 00038 #include "mbedtls/platform.h" 00039 #else 00040 #include <stdio.h> 00041 #define mbedtls_printf printf 00042 #endif /* MBEDTLS_PLATFORM_C */ 00043 #endif /* MBEDTLS_SELF_TEST */ 00044 00045 #if !defined(MBEDTLS_CHACHAPOLY_ALT) 00046 00047 /* Parameter validation macros */ 00048 #define CHACHAPOLY_VALIDATE_RET( cond ) \ 00049 MBEDTLS_INTERNAL_VALIDATE_RET( cond, MBEDTLS_ERR_POLY1305_BAD_INPUT_DATA ) 00050 #define CHACHAPOLY_VALIDATE( cond ) \ 00051 MBEDTLS_INTERNAL_VALIDATE( cond ) 00052 00053 #define CHACHAPOLY_STATE_INIT ( 0 ) 00054 #define CHACHAPOLY_STATE_AAD ( 1 ) 00055 #define CHACHAPOLY_STATE_CIPHERTEXT ( 2 ) /* Encrypting or decrypting */ 00056 #define CHACHAPOLY_STATE_FINISHED ( 3 ) 00057 00058 /** 00059 * \brief Adds nul bytes to pad the AAD for Poly1305. 00060 * 00061 * \param ctx The ChaCha20-Poly1305 context. 00062 */ 00063 static int chachapoly_pad_aad( mbedtls_chachapoly_context *ctx ) 00064 { 00065 uint32_t partial_block_len = (uint32_t) ( ctx->aad_len % 16U ); 00066 unsigned char zeroes[15]; 00067 00068 if( partial_block_len == 0U ) 00069 return( 0 ); 00070 00071 memset( zeroes, 0, sizeof( zeroes ) ); 00072 00073 return( mbedtls_poly1305_update( &ctx->poly1305_ctx, 00074 zeroes, 00075 16U - partial_block_len ) ); 00076 } 00077 00078 /** 00079 * \brief Adds nul bytes to pad the ciphertext for Poly1305. 00080 * 00081 * \param ctx The ChaCha20-Poly1305 context. 00082 */ 00083 static int chachapoly_pad_ciphertext( mbedtls_chachapoly_context *ctx ) 00084 { 00085 uint32_t partial_block_len = (uint32_t) ( ctx->ciphertext_len % 16U ); 00086 unsigned char zeroes[15]; 00087 00088 if( partial_block_len == 0U ) 00089 return( 0 ); 00090 00091 memset( zeroes, 0, sizeof( zeroes ) ); 00092 return( mbedtls_poly1305_update( &ctx->poly1305_ctx, 00093 zeroes, 00094 16U - partial_block_len ) ); 00095 } 00096 00097 void mbedtls_chachapoly_init( mbedtls_chachapoly_context *ctx ) 00098 { 00099 CHACHAPOLY_VALIDATE( ctx != NULL ); 00100 00101 mbedtls_chacha20_init( &ctx->chacha20_ctx ); 00102 mbedtls_poly1305_init( &ctx->poly1305_ctx ); 00103 ctx->aad_len = 0U; 00104 ctx->ciphertext_len = 0U; 00105 ctx->state = CHACHAPOLY_STATE_INIT; 00106 ctx->mode = MBEDTLS_CHACHAPOLY_ENCRYPT; 00107 } 00108 00109 void mbedtls_chachapoly_free( mbedtls_chachapoly_context *ctx ) 00110 { 00111 if( ctx == NULL ) 00112 return; 00113 00114 mbedtls_chacha20_free( &ctx->chacha20_ctx ); 00115 mbedtls_poly1305_free( &ctx->poly1305_ctx ); 00116 ctx->aad_len = 0U; 00117 ctx->ciphertext_len = 0U; 00118 ctx->state = CHACHAPOLY_STATE_INIT; 00119 ctx->mode = MBEDTLS_CHACHAPOLY_ENCRYPT; 00120 } 00121 00122 int mbedtls_chachapoly_setkey( mbedtls_chachapoly_context *ctx, 00123 const unsigned char key[32] ) 00124 { 00125 int ret; 00126 CHACHAPOLY_VALIDATE_RET( ctx != NULL ); 00127 CHACHAPOLY_VALIDATE_RET( key != NULL ); 00128 00129 ret = mbedtls_chacha20_setkey( &ctx->chacha20_ctx, key ); 00130 00131 return( ret ); 00132 } 00133 00134 int mbedtls_chachapoly_starts( mbedtls_chachapoly_context *ctx, 00135 const unsigned char nonce[12], 00136 mbedtls_chachapoly_mode_t mode ) 00137 { 00138 int ret; 00139 unsigned char poly1305_key[64]; 00140 CHACHAPOLY_VALIDATE_RET( ctx != NULL ); 00141 CHACHAPOLY_VALIDATE_RET( nonce != NULL ); 00142 00143 /* Set counter = 0, will be update to 1 when generating Poly1305 key */ 00144 ret = mbedtls_chacha20_starts( &ctx->chacha20_ctx, nonce, 0U ); 00145 if( ret != 0 ) 00146 goto cleanup; 00147 00148 /* Generate the Poly1305 key by getting the ChaCha20 keystream output with 00149 * counter = 0. This is the same as encrypting a buffer of zeroes. 00150 * Only the first 256-bits (32 bytes) of the key is used for Poly1305. 00151 * The other 256 bits are discarded. 00152 */ 00153 memset( poly1305_key, 0, sizeof( poly1305_key ) ); 00154 ret = mbedtls_chacha20_update( &ctx->chacha20_ctx, sizeof( poly1305_key ), 00155 poly1305_key, poly1305_key ); 00156 if( ret != 0 ) 00157 goto cleanup; 00158 00159 ret = mbedtls_poly1305_starts( &ctx->poly1305_ctx, poly1305_key ); 00160 00161 if( ret == 0 ) 00162 { 00163 ctx->aad_len = 0U; 00164 ctx->ciphertext_len = 0U; 00165 ctx->state = CHACHAPOLY_STATE_AAD; 00166 ctx->mode = mode; 00167 } 00168 00169 cleanup: 00170 mbedtls_platform_zeroize( poly1305_key, 64U ); 00171 return( ret ); 00172 } 00173 00174 int mbedtls_chachapoly_update_aad( mbedtls_chachapoly_context *ctx, 00175 const unsigned char *aad, 00176 size_t aad_len ) 00177 { 00178 CHACHAPOLY_VALIDATE_RET( ctx != NULL ); 00179 CHACHAPOLY_VALIDATE_RET( aad_len == 0 || aad != NULL ); 00180 00181 if( ctx->state != CHACHAPOLY_STATE_AAD ) 00182 return( MBEDTLS_ERR_CHACHAPOLY_BAD_STATE ); 00183 00184 ctx->aad_len += aad_len; 00185 00186 return( mbedtls_poly1305_update( &ctx->poly1305_ctx, aad, aad_len ) ); 00187 } 00188 00189 int mbedtls_chachapoly_update( mbedtls_chachapoly_context *ctx, 00190 size_t len, 00191 const unsigned char *input, 00192 unsigned char *output ) 00193 { 00194 int ret; 00195 CHACHAPOLY_VALIDATE_RET( ctx != NULL ); 00196 CHACHAPOLY_VALIDATE_RET( len == 0 || input != NULL ); 00197 CHACHAPOLY_VALIDATE_RET( len == 0 || output != NULL ); 00198 00199 if( ( ctx->state != CHACHAPOLY_STATE_AAD ) && 00200 ( ctx->state != CHACHAPOLY_STATE_CIPHERTEXT ) ) 00201 { 00202 return( MBEDTLS_ERR_CHACHAPOLY_BAD_STATE ); 00203 } 00204 00205 if( ctx->state == CHACHAPOLY_STATE_AAD ) 00206 { 00207 ctx->state = CHACHAPOLY_STATE_CIPHERTEXT; 00208 00209 ret = chachapoly_pad_aad( ctx ); 00210 if( ret != 0 ) 00211 return( ret ); 00212 } 00213 00214 ctx->ciphertext_len += len; 00215 00216 if( ctx->mode == MBEDTLS_CHACHAPOLY_ENCRYPT ) 00217 { 00218 ret = mbedtls_chacha20_update( &ctx->chacha20_ctx, len, input, output ); 00219 if( ret != 0 ) 00220 return( ret ); 00221 00222 ret = mbedtls_poly1305_update( &ctx->poly1305_ctx, output, len ); 00223 if( ret != 0 ) 00224 return( ret ); 00225 } 00226 else /* DECRYPT */ 00227 { 00228 ret = mbedtls_poly1305_update( &ctx->poly1305_ctx, input, len ); 00229 if( ret != 0 ) 00230 return( ret ); 00231 00232 ret = mbedtls_chacha20_update( &ctx->chacha20_ctx, len, input, output ); 00233 if( ret != 0 ) 00234 return( ret ); 00235 } 00236 00237 return( 0 ); 00238 } 00239 00240 int mbedtls_chachapoly_finish( mbedtls_chachapoly_context *ctx, 00241 unsigned char mac[16] ) 00242 { 00243 int ret; 00244 unsigned char len_block[16]; 00245 CHACHAPOLY_VALIDATE_RET( ctx != NULL ); 00246 CHACHAPOLY_VALIDATE_RET( mac != NULL ); 00247 00248 if( ctx->state == CHACHAPOLY_STATE_INIT ) 00249 { 00250 return( MBEDTLS_ERR_CHACHAPOLY_BAD_STATE ); 00251 } 00252 00253 if( ctx->state == CHACHAPOLY_STATE_AAD ) 00254 { 00255 ret = chachapoly_pad_aad( ctx ); 00256 if( ret != 0 ) 00257 return( ret ); 00258 } 00259 else if( ctx->state == CHACHAPOLY_STATE_CIPHERTEXT ) 00260 { 00261 ret = chachapoly_pad_ciphertext( ctx ); 00262 if( ret != 0 ) 00263 return( ret ); 00264 } 00265 00266 ctx->state = CHACHAPOLY_STATE_FINISHED; 00267 00268 /* The lengths of the AAD and ciphertext are processed by 00269 * Poly1305 as the final 128-bit block, encoded as little-endian integers. 00270 */ 00271 len_block[ 0] = (unsigned char)( ctx->aad_len ); 00272 len_block[ 1] = (unsigned char)( ctx->aad_len >> 8 ); 00273 len_block[ 2] = (unsigned char)( ctx->aad_len >> 16 ); 00274 len_block[ 3] = (unsigned char)( ctx->aad_len >> 24 ); 00275 len_block[ 4] = (unsigned char)( ctx->aad_len >> 32 ); 00276 len_block[ 5] = (unsigned char)( ctx->aad_len >> 40 ); 00277 len_block[ 6] = (unsigned char)( ctx->aad_len >> 48 ); 00278 len_block[ 7] = (unsigned char)( ctx->aad_len >> 56 ); 00279 len_block[ 8] = (unsigned char)( ctx->ciphertext_len ); 00280 len_block[ 9] = (unsigned char)( ctx->ciphertext_len >> 8 ); 00281 len_block[10] = (unsigned char)( ctx->ciphertext_len >> 16 ); 00282 len_block[11] = (unsigned char)( ctx->ciphertext_len >> 24 ); 00283 len_block[12] = (unsigned char)( ctx->ciphertext_len >> 32 ); 00284 len_block[13] = (unsigned char)( ctx->ciphertext_len >> 40 ); 00285 len_block[14] = (unsigned char)( ctx->ciphertext_len >> 48 ); 00286 len_block[15] = (unsigned char)( ctx->ciphertext_len >> 56 ); 00287 00288 ret = mbedtls_poly1305_update( &ctx->poly1305_ctx, len_block, 16U ); 00289 if( ret != 0 ) 00290 return( ret ); 00291 00292 ret = mbedtls_poly1305_finish( &ctx->poly1305_ctx, mac ); 00293 00294 return( ret ); 00295 } 00296 00297 static int chachapoly_crypt_and_tag( mbedtls_chachapoly_context *ctx, 00298 mbedtls_chachapoly_mode_t mode, 00299 size_t length, 00300 const unsigned char nonce[12], 00301 const unsigned char *aad, 00302 size_t aad_len, 00303 const unsigned char *input, 00304 unsigned char *output, 00305 unsigned char tag[16] ) 00306 { 00307 int ret; 00308 00309 ret = mbedtls_chachapoly_starts( ctx, nonce, mode ); 00310 if( ret != 0 ) 00311 goto cleanup; 00312 00313 ret = mbedtls_chachapoly_update_aad( ctx, aad, aad_len ); 00314 if( ret != 0 ) 00315 goto cleanup; 00316 00317 ret = mbedtls_chachapoly_update( ctx, length, input, output ); 00318 if( ret != 0 ) 00319 goto cleanup; 00320 00321 ret = mbedtls_chachapoly_finish( ctx, tag ); 00322 00323 cleanup: 00324 return( ret ); 00325 } 00326 00327 int mbedtls_chachapoly_encrypt_and_tag( mbedtls_chachapoly_context *ctx, 00328 size_t length, 00329 const unsigned char nonce[12], 00330 const unsigned char *aad, 00331 size_t aad_len, 00332 const unsigned char *input, 00333 unsigned char *output, 00334 unsigned char tag[16] ) 00335 { 00336 CHACHAPOLY_VALIDATE_RET( ctx != NULL ); 00337 CHACHAPOLY_VALIDATE_RET( nonce != NULL ); 00338 CHACHAPOLY_VALIDATE_RET( tag != NULL ); 00339 CHACHAPOLY_VALIDATE_RET( aad_len == 0 || aad != NULL ); 00340 CHACHAPOLY_VALIDATE_RET( length == 0 || input != NULL ); 00341 CHACHAPOLY_VALIDATE_RET( length == 0 || output != NULL ); 00342 00343 return( chachapoly_crypt_and_tag( ctx, MBEDTLS_CHACHAPOLY_ENCRYPT, 00344 length, nonce, aad, aad_len, 00345 input, output, tag ) ); 00346 } 00347 00348 int mbedtls_chachapoly_auth_decrypt( mbedtls_chachapoly_context *ctx, 00349 size_t length, 00350 const unsigned char nonce[12], 00351 const unsigned char *aad, 00352 size_t aad_len, 00353 const unsigned char tag[16], 00354 const unsigned char *input, 00355 unsigned char *output ) 00356 { 00357 int ret; 00358 unsigned char check_tag[16]; 00359 size_t i; 00360 int diff; 00361 CHACHAPOLY_VALIDATE_RET( ctx != NULL ); 00362 CHACHAPOLY_VALIDATE_RET( nonce != NULL ); 00363 CHACHAPOLY_VALIDATE_RET( tag != NULL ); 00364 CHACHAPOLY_VALIDATE_RET( aad_len == 0 || aad != NULL ); 00365 CHACHAPOLY_VALIDATE_RET( length == 0 || input != NULL ); 00366 CHACHAPOLY_VALIDATE_RET( length == 0 || output != NULL ); 00367 00368 if( ( ret = chachapoly_crypt_and_tag( ctx, 00369 MBEDTLS_CHACHAPOLY_DECRYPT, length, nonce, 00370 aad, aad_len, input, output, check_tag ) ) != 0 ) 00371 { 00372 return( ret ); 00373 } 00374 00375 /* Check tag in "constant-time" */ 00376 for( diff = 0, i = 0; i < sizeof( check_tag ); i++ ) 00377 diff |= tag[i] ^ check_tag[i]; 00378 00379 if( diff != 0 ) 00380 { 00381 mbedtls_platform_zeroize( output, length ); 00382 return( MBEDTLS_ERR_CHACHAPOLY_AUTH_FAILED ); 00383 } 00384 00385 return( 0 ); 00386 } 00387 00388 #endif /* MBEDTLS_CHACHAPOLY_ALT */ 00389 00390 #if defined(MBEDTLS_SELF_TEST) 00391 00392 static const unsigned char test_key[1][32] = 00393 { 00394 { 00395 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 00396 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 00397 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 00398 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f 00399 } 00400 }; 00401 00402 static const unsigned char test_nonce[1][12] = 00403 { 00404 { 00405 0x07, 0x00, 0x00, 0x00, /* 32-bit common part */ 00406 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 /* 64-bit IV */ 00407 } 00408 }; 00409 00410 static const unsigned char test_aad[1][12] = 00411 { 00412 { 00413 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 00414 0xc4, 0xc5, 0xc6, 0xc7 00415 } 00416 }; 00417 00418 static const size_t test_aad_len[1] = 00419 { 00420 12U 00421 }; 00422 00423 static const unsigned char test_input[1][114] = 00424 { 00425 { 00426 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 00427 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c, 00428 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 00429 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73, 00430 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, 00431 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63, 00432 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, 00433 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f, 00434 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 00435 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20, 00436 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, 00437 0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73, 00438 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, 00439 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69, 00440 0x74, 0x2e 00441 } 00442 }; 00443 00444 static const unsigned char test_output[1][114] = 00445 { 00446 { 00447 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 00448 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2, 00449 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x08, 0xfe, 00450 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6, 00451 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 00452 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b, 00453 0x1a, 0x71, 0xde, 0x0a, 0x9e, 0x06, 0x0b, 0x29, 00454 0x05, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36, 00455 0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c, 00456 0x98, 0x03, 0xae, 0xe3, 0x28, 0x09, 0x1b, 0x58, 00457 0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94, 00458 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc, 00459 0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d, 00460 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b, 00461 0x61, 0x16 00462 } 00463 }; 00464 00465 static const size_t test_input_len[1] = 00466 { 00467 114U 00468 }; 00469 00470 static const unsigned char test_mac[1][16] = 00471 { 00472 { 00473 0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a, 00474 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, 0x06, 0x91 00475 } 00476 }; 00477 00478 #define ASSERT( cond, args ) \ 00479 do \ 00480 { \ 00481 if( ! ( cond ) ) \ 00482 { \ 00483 if( verbose != 0 ) \ 00484 mbedtls_printf args; \ 00485 \ 00486 return( -1 ); \ 00487 } \ 00488 } \ 00489 while( 0 ) 00490 00491 int mbedtls_chachapoly_self_test( int verbose ) 00492 { 00493 mbedtls_chachapoly_context ctx; 00494 unsigned i; 00495 int ret; 00496 unsigned char output[200]; 00497 unsigned char mac[16]; 00498 00499 for( i = 0U; i < 1U; i++ ) 00500 { 00501 if( verbose != 0 ) 00502 mbedtls_printf( " ChaCha20-Poly1305 test %u ", i ); 00503 00504 mbedtls_chachapoly_init( &ctx ); 00505 00506 ret = mbedtls_chachapoly_setkey( &ctx, test_key[i] ); 00507 ASSERT( 0 == ret, ( "setkey() error code: %i\n", ret ) ); 00508 00509 ret = mbedtls_chachapoly_encrypt_and_tag( &ctx, 00510 test_input_len[i], 00511 test_nonce[i], 00512 test_aad[i], 00513 test_aad_len[i], 00514 test_input[i], 00515 output, 00516 mac ); 00517 00518 ASSERT( 0 == ret, ( "crypt_and_tag() error code: %i\n", ret ) ); 00519 00520 ASSERT( 0 == memcmp( output, test_output[i], test_input_len[i] ), 00521 ( "failure (wrong output)\n" ) ); 00522 00523 ASSERT( 0 == memcmp( mac, test_mac[i], 16U ), 00524 ( "failure (wrong MAC)\n" ) ); 00525 00526 mbedtls_chachapoly_free( &ctx ); 00527 00528 if( verbose != 0 ) 00529 mbedtls_printf( "passed\n" ); 00530 } 00531 00532 if( verbose != 0 ) 00533 mbedtls_printf( "\n" ); 00534 00535 return( 0 ); 00536 } 00537 00538 #endif /* MBEDTLS_SELF_TEST */ 00539 00540 #endif /* MBEDTLS_CHACHAPOLY_C */
Generated on Tue Jul 12 2022 13:54:06 by
