1 /* 2 * Copyright 2023-2025 The OpenSSL Project Authors. All Rights Reserved. 3 * 4 * Licensed under the Apache License 2.0 (the "License"). You may not use 5 * this file except in compliance with the License. You can obtain a copy 6 * in the file LICENSE in the source distribution or at 7 * https://www.openssl.org/source/license.html 8 */ 9 10 #include "internal/qlog_event_helpers.h" 11 #include "internal/common.h" 12 #include "internal/packet.h" 13 #include "internal/quic_channel.h" 14 #include "internal/quic_error.h" 15 16 void ossl_qlog_event_connectivity_connection_started(QLOG *qlog, 17 const QUIC_CONN_ID *init_dcid) 18 { 19 #ifndef OPENSSL_NO_QLOG 20 QLOG_EVENT_BEGIN(qlog, connectivity, connection_started) 21 QLOG_STR("protocol", "quic"); 22 QLOG_CID("dst_cid", init_dcid); 23 QLOG_EVENT_END() 24 #endif 25 } 26 27 #ifndef OPENSSL_NO_QLOG 28 static const char *map_state_to_qlog(uint32_t state, 29 int handshake_complete, 30 int handshake_confirmed) 31 { 32 switch (state) { 33 default: 34 case QUIC_CHANNEL_STATE_IDLE: 35 return NULL; 36 37 case QUIC_CHANNEL_STATE_ACTIVE: 38 if (handshake_confirmed) 39 return "handshake_confirmed"; 40 else if (handshake_complete) 41 return "handshake_complete"; 42 else 43 return "attempted"; 44 45 case QUIC_CHANNEL_STATE_TERMINATING_CLOSING: 46 return "closing"; 47 48 case QUIC_CHANNEL_STATE_TERMINATING_DRAINING: 49 return "draining"; 50 51 case QUIC_CHANNEL_STATE_TERMINATED: 52 return "closed"; 53 } 54 } 55 #endif 56 57 void ossl_qlog_event_connectivity_connection_state_updated(QLOG *qlog, 58 uint32_t old_state, 59 uint32_t new_state, 60 int handshake_complete, 61 int handshake_confirmed) 62 { 63 #ifndef OPENSSL_NO_QLOG 64 const char *state_s; 65 66 QLOG_EVENT_BEGIN(qlog, connectivity, connection_state_updated) 67 state_s = map_state_to_qlog(new_state, 68 handshake_complete, 69 handshake_confirmed); 70 71 if (state_s != NULL) 72 QLOG_STR("state", state_s); 73 QLOG_EVENT_END() 74 #endif 75 } 76 77 #ifndef OPENSSL_NO_QLOG 78 static const char *quic_err_to_qlog(uint64_t error_code) 79 { 80 switch (error_code) { 81 case OSSL_QUIC_ERR_INTERNAL_ERROR: 82 return "internal_error"; 83 case OSSL_QUIC_ERR_CONNECTION_REFUSED: 84 return "connection_refused"; 85 case OSSL_QUIC_ERR_FLOW_CONTROL_ERROR: 86 return "flow_control_error"; 87 case OSSL_QUIC_ERR_STREAM_LIMIT_ERROR: 88 return "stream_limit_error"; 89 case OSSL_QUIC_ERR_STREAM_STATE_ERROR: 90 return "stream_state_error"; 91 case OSSL_QUIC_ERR_FINAL_SIZE_ERROR: 92 return "final_size_error"; 93 case OSSL_QUIC_ERR_FRAME_ENCODING_ERROR: 94 return "frame_encoding_error"; 95 case OSSL_QUIC_ERR_TRANSPORT_PARAMETER_ERROR: 96 return "transport_parameter_error"; 97 case OSSL_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR: 98 return "connection_id_limit_error"; 99 case OSSL_QUIC_ERR_PROTOCOL_VIOLATION: 100 return "protocol_violation"; 101 case OSSL_QUIC_ERR_INVALID_TOKEN: 102 return "invalid_token"; 103 case OSSL_QUIC_ERR_APPLICATION_ERROR: 104 return "application_error"; 105 case OSSL_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED: 106 return "crypto_buffer_exceeded"; 107 case OSSL_QUIC_ERR_KEY_UPDATE_ERROR: 108 return "key_update_error"; 109 case OSSL_QUIC_ERR_AEAD_LIMIT_REACHED: 110 return "aead_limit_reached"; 111 case OSSL_QUIC_ERR_NO_VIABLE_PATH: 112 return "no_viable_path"; 113 default: 114 return NULL; 115 } 116 } 117 #endif 118 119 void ossl_qlog_event_connectivity_connection_closed(QLOG *qlog, 120 const QUIC_TERMINATE_CAUSE *tcause) 121 { 122 #ifndef OPENSSL_NO_QLOG 123 QLOG_EVENT_BEGIN(qlog, connectivity, connection_closed) 124 QLOG_STR("owner", tcause->remote ? "remote" : "local"); 125 if (tcause->app) { 126 QLOG_U64("application_code", tcause->error_code); 127 } else { 128 const char *m = quic_err_to_qlog(tcause->error_code); 129 char ce[32]; 130 131 if (tcause->error_code >= OSSL_QUIC_ERR_CRYPTO_ERR_BEGIN 132 && tcause->error_code <= OSSL_QUIC_ERR_CRYPTO_ERR_END) { 133 BIO_snprintf(ce, sizeof(ce), "crypto_error_0x%03llx", 134 (unsigned long long)tcause->error_code); 135 m = ce; 136 } 137 /* TODO(QLOG FUTURE): Consider adding ERR information in the output. */ 138 139 if (m != NULL) 140 QLOG_STR("connection_code", m); 141 else 142 QLOG_U64("connection_code", tcause->error_code); 143 } 144 145 QLOG_STR_LEN("reason", tcause->reason, tcause->reason_len); 146 QLOG_EVENT_END() 147 #endif 148 } 149 150 #ifndef OPENSSL_NO_QLOG 151 static const char *quic_pkt_type_to_qlog(uint32_t pkt_type) 152 { 153 switch (pkt_type) { 154 case QUIC_PKT_TYPE_INITIAL: 155 return "initial"; 156 case QUIC_PKT_TYPE_HANDSHAKE: 157 return "handshake"; 158 case QUIC_PKT_TYPE_0RTT: 159 return "0RTT"; 160 case QUIC_PKT_TYPE_1RTT: 161 return "1RTT"; 162 case QUIC_PKT_TYPE_VERSION_NEG: 163 return "version_negotiation"; 164 case QUIC_PKT_TYPE_RETRY: 165 return "retry"; 166 default: 167 return "unknown"; 168 } 169 } 170 #endif 171 172 void ossl_qlog_event_recovery_packet_lost(QLOG *qlog, 173 const QUIC_TXPIM_PKT *tpkt) 174 { 175 #ifndef OPENSSL_NO_QLOG 176 QLOG_EVENT_BEGIN(qlog, recovery, packet_lost) 177 QLOG_BEGIN("header") 178 QLOG_STR("packet_type", quic_pkt_type_to_qlog(tpkt->pkt_type)); 179 if (ossl_quic_pkt_type_has_pn(tpkt->pkt_type)) 180 QLOG_U64("packet_number", tpkt->ackm_pkt.pkt_num); 181 QLOG_END() 182 QLOG_EVENT_END() 183 #endif 184 } 185 186 #ifndef OPENSSL_NO_QLOG 187 # define MAX_ACK_RANGES 32 188 189 static void ignore_res(int x) {} 190 191 /* 192 * For logging received packets, we need to parse all the frames in the packet 193 * to log them. We should do this separately to the RXDP code because we want to 194 * log the packet and its contents before we start to actually process it in 195 * case it causes an error. We also in general don't want to do other 196 * non-logging related work in the middle of an event logging transaction. 197 * Reparsing packet data allows us to meet these needs while avoiding the need 198 * to keep around bookkeeping data on what frames were in a packet, etc. 199 * 200 * For logging transmitted packets, we actually reuse the same code and reparse 201 * the outgoing packet's payload. This again has the advantage that we only log 202 * a packet when it is actually queued for transmission (and not if something 203 * goes wrong before then) while avoiding the need to keep around bookkeeping 204 * data on what frames it contained. 205 */ 206 static int log_frame_actual(QLOG *qlog_instance, PACKET *pkt, 207 size_t *need_skip) 208 { 209 uint64_t frame_type; 210 OSSL_QUIC_FRAME_ACK ack; 211 OSSL_QUIC_ACK_RANGE ack_ranges[MAX_ACK_RANGES]; 212 uint64_t num_ranges, total_ranges; 213 size_t i; 214 PACKET orig_pkt = *pkt; 215 216 if (!ossl_quic_wire_peek_frame_header(pkt, &frame_type, NULL)) { 217 *need_skip = SIZE_MAX; 218 return 0; 219 } 220 221 /* 222 * If something goes wrong decoding a frame we cannot log it as that frame 223 * as we need to know how to decode it in order to be able to do so, but in 224 * that case we log it as an unknown frame to assist with diagnosis. 225 */ 226 switch (frame_type) { 227 case OSSL_QUIC_FRAME_TYPE_PADDING: 228 QLOG_STR("frame_type", "padding"); 229 QLOG_U64("payload_length", 230 ossl_quic_wire_decode_padding(pkt)); 231 break; 232 case OSSL_QUIC_FRAME_TYPE_PING: 233 if (!ossl_quic_wire_decode_frame_ping(pkt)) 234 goto unknown; 235 236 QLOG_STR("frame_type", "ping"); 237 break; 238 case OSSL_QUIC_FRAME_TYPE_ACK_WITHOUT_ECN: 239 case OSSL_QUIC_FRAME_TYPE_ACK_WITH_ECN: 240 if (!ossl_quic_wire_peek_frame_ack_num_ranges(pkt, &num_ranges)) 241 goto unknown; 242 243 ack.ack_ranges = ack_ranges; 244 ack.num_ack_ranges = OSSL_NELEM(ack_ranges); 245 if (!ossl_quic_wire_decode_frame_ack(pkt, 3, &ack, &total_ranges)) 246 goto unknown; 247 248 QLOG_STR("frame_type", "ack"); 249 QLOG_U64("ack_delay", ossl_time2ms(ack.delay_time)); 250 if (ack.ecn_present) { 251 QLOG_U64("ect1", ack.ect0); 252 QLOG_U64("ect0", ack.ect1); 253 QLOG_U64("ce", ack.ecnce); 254 } 255 QLOG_BEGIN_ARRAY("acked_ranges"); 256 for (i = 0; i < ack.num_ack_ranges; ++i) { 257 QLOG_BEGIN_ARRAY(NULL) 258 QLOG_U64(NULL, ack.ack_ranges[i].start); 259 if (ack.ack_ranges[i].end != ack.ack_ranges[i].start) 260 QLOG_U64(NULL, ack.ack_ranges[i].end); 261 QLOG_END_ARRAY() 262 } 263 QLOG_END_ARRAY() 264 break; 265 case OSSL_QUIC_FRAME_TYPE_RESET_STREAM: 266 { 267 OSSL_QUIC_FRAME_RESET_STREAM f; 268 269 if (!ossl_quic_wire_decode_frame_reset_stream(pkt, &f)) 270 goto unknown; 271 272 QLOG_STR("frame_type", "reset_stream"); 273 QLOG_U64("stream_id", f.stream_id); 274 QLOG_U64("error_code", f.app_error_code); 275 QLOG_U64("final_size", f.final_size); 276 } 277 break; 278 case OSSL_QUIC_FRAME_TYPE_STOP_SENDING: 279 { 280 OSSL_QUIC_FRAME_STOP_SENDING f; 281 282 if (!ossl_quic_wire_decode_frame_stop_sending(pkt, &f)) 283 goto unknown; 284 285 QLOG_STR("frame_type", "stop_sending"); 286 QLOG_U64("stream_id", f.stream_id); 287 QLOG_U64("error_code", f.app_error_code); 288 } 289 break; 290 case OSSL_QUIC_FRAME_TYPE_CRYPTO: 291 { 292 OSSL_QUIC_FRAME_CRYPTO f; 293 294 if (!ossl_quic_wire_decode_frame_crypto(pkt, 1, &f)) 295 goto unknown; 296 297 QLOG_STR("frame_type", "crypto"); 298 QLOG_U64("offset", f.offset); 299 QLOG_U64("payload_length", f.len); 300 *need_skip += (size_t)f.len; 301 } 302 break; 303 case OSSL_QUIC_FRAME_TYPE_STREAM: 304 case OSSL_QUIC_FRAME_TYPE_STREAM_FIN: 305 case OSSL_QUIC_FRAME_TYPE_STREAM_LEN: 306 case OSSL_QUIC_FRAME_TYPE_STREAM_LEN_FIN: 307 case OSSL_QUIC_FRAME_TYPE_STREAM_OFF: 308 case OSSL_QUIC_FRAME_TYPE_STREAM_OFF_FIN: 309 case OSSL_QUIC_FRAME_TYPE_STREAM_OFF_LEN: 310 case OSSL_QUIC_FRAME_TYPE_STREAM_OFF_LEN_FIN: 311 { 312 OSSL_QUIC_FRAME_STREAM f; 313 314 if (!ossl_quic_wire_decode_frame_stream(pkt, 1, &f)) 315 goto unknown; 316 317 QLOG_STR("frame_type", "stream"); 318 QLOG_U64("stream_id", f.stream_id); 319 QLOG_U64("offset", f.offset); 320 QLOG_U64("payload_length", f.len); 321 QLOG_BOOL("explicit_length", f.has_explicit_len); 322 if (f.is_fin) 323 QLOG_BOOL("fin", 1); 324 *need_skip = f.has_explicit_len 325 ? *need_skip + (size_t)f.len : SIZE_MAX; 326 } 327 break; 328 case OSSL_QUIC_FRAME_TYPE_MAX_DATA: 329 { 330 uint64_t x; 331 332 if (!ossl_quic_wire_decode_frame_max_data(pkt, &x)) 333 goto unknown; 334 335 QLOG_STR("frame_type", "max_data"); 336 QLOG_U64("maximum", x); 337 } 338 break; 339 case OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_BIDI: 340 case OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_UNI: 341 { 342 uint64_t x; 343 344 if (!ossl_quic_wire_decode_frame_max_streams(pkt, &x)) 345 goto unknown; 346 347 QLOG_STR("frame_type", "max_streams"); 348 QLOG_STR("stream_type", 349 frame_type == OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_BIDI 350 ? "bidirectional" : "unidirectional"); 351 QLOG_U64("maximum", x); 352 } 353 break; 354 case OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA: 355 { 356 uint64_t stream_id, max_data; 357 358 if (!ossl_quic_wire_decode_frame_max_stream_data(pkt, &stream_id, 359 &max_data)) 360 goto unknown; 361 362 QLOG_STR("frame_type", "max_stream_data"); 363 QLOG_U64("stream_id", stream_id); 364 QLOG_U64("maximum", max_data); 365 } 366 break; 367 case OSSL_QUIC_FRAME_TYPE_PATH_CHALLENGE: 368 { 369 uint64_t challenge; 370 371 if (!ossl_quic_wire_decode_frame_path_challenge(pkt, &challenge)) 372 goto unknown; 373 374 QLOG_STR("frame_type", "path_challenge"); 375 } 376 break; 377 case OSSL_QUIC_FRAME_TYPE_PATH_RESPONSE: 378 { 379 uint64_t challenge; 380 381 if (!ossl_quic_wire_decode_frame_path_response(pkt, &challenge)) 382 goto unknown; 383 384 QLOG_STR("frame_type", "path_response"); 385 } 386 break; 387 case OSSL_QUIC_FRAME_TYPE_CONN_CLOSE_APP: 388 case OSSL_QUIC_FRAME_TYPE_CONN_CLOSE_TRANSPORT: 389 { 390 OSSL_QUIC_FRAME_CONN_CLOSE f; 391 392 if (!ossl_quic_wire_decode_frame_conn_close(pkt, &f)) 393 goto unknown; 394 395 QLOG_STR("frame_type", "connection_close"); 396 QLOG_STR("error_space", f.is_app ? "application" : "transport"); 397 QLOG_U64("error_code_value", f.error_code); 398 if (f.is_app) 399 QLOG_U64("error_code", f.error_code); 400 if (!f.is_app && f.frame_type != 0) 401 QLOG_U64("trigger_frame_type", f.frame_type); 402 QLOG_STR_LEN("reason", f.reason, f.reason_len); 403 } 404 break; 405 case OSSL_QUIC_FRAME_TYPE_HANDSHAKE_DONE: 406 { 407 if (!ossl_quic_wire_decode_frame_handshake_done(pkt)) 408 goto unknown; 409 410 QLOG_STR("frame_type", "handshake_done"); 411 } 412 break; 413 case OSSL_QUIC_FRAME_TYPE_NEW_CONN_ID: 414 { 415 OSSL_QUIC_FRAME_NEW_CONN_ID f; 416 417 if (!ossl_quic_wire_decode_frame_new_conn_id(pkt, &f)) 418 goto unknown; 419 420 QLOG_STR("frame_type", "new_connection_id"); 421 QLOG_U64("sequence_number", f.seq_num); 422 QLOG_U64("retire_prior_to", f.retire_prior_to); 423 QLOG_CID("connection_id", &f.conn_id); 424 QLOG_BIN("stateless_reset_token", 425 f.stateless_reset.token, 426 sizeof(f.stateless_reset.token)); 427 } 428 break; 429 case OSSL_QUIC_FRAME_TYPE_RETIRE_CONN_ID: 430 { 431 uint64_t seq_num; 432 433 if (!ossl_quic_wire_decode_frame_retire_conn_id(pkt, &seq_num)) 434 goto unknown; 435 436 QLOG_STR("frame_type", "retire_connection_id"); 437 QLOG_U64("sequence_number", seq_num); 438 } 439 break; 440 case OSSL_QUIC_FRAME_TYPE_DATA_BLOCKED: 441 { 442 uint64_t x; 443 444 if (!ossl_quic_wire_decode_frame_data_blocked(pkt, &x)) 445 goto unknown; 446 447 QLOG_STR("frame_type", "data_blocked"); 448 QLOG_U64("limit", x); 449 } 450 break; 451 case OSSL_QUIC_FRAME_TYPE_STREAM_DATA_BLOCKED: 452 { 453 uint64_t stream_id, x; 454 455 if (!ossl_quic_wire_decode_frame_stream_data_blocked(pkt, 456 &stream_id, 457 &x)) 458 goto unknown; 459 460 QLOG_STR("frame_type", "stream_data_blocked"); 461 QLOG_U64("stream_id", stream_id); 462 QLOG_U64("limit", x); 463 } 464 break; 465 case OSSL_QUIC_FRAME_TYPE_STREAMS_BLOCKED_BIDI: 466 case OSSL_QUIC_FRAME_TYPE_STREAMS_BLOCKED_UNI: 467 { 468 uint64_t x; 469 470 if (!ossl_quic_wire_decode_frame_streams_blocked(pkt, &x)) 471 goto unknown; 472 473 QLOG_STR("frame_type", "streams_blocked"); 474 QLOG_STR("stream_type", 475 frame_type == OSSL_QUIC_FRAME_TYPE_STREAMS_BLOCKED_BIDI 476 ? "bidirectional" : "unidirectional"); 477 QLOG_U64("limit", x); 478 } 479 break; 480 case OSSL_QUIC_FRAME_TYPE_NEW_TOKEN: 481 { 482 const unsigned char *token; 483 size_t token_len; 484 485 if (!ossl_quic_wire_decode_frame_new_token(pkt, &token, &token_len)) 486 goto unknown; 487 488 QLOG_STR("frame_type", "new_token"); 489 QLOG_BEGIN("token"); 490 QLOG_BEGIN("raw"); 491 QLOG_BIN("data", token, token_len); 492 QLOG_END(); 493 QLOG_END(); 494 } 495 break; 496 default: 497 unknown: 498 QLOG_STR("frame_type", "unknown"); 499 QLOG_U64("frame_type_value", frame_type); 500 501 /* 502 * Can't continue scanning for frames in this case as the frame length 503 * is unknown. We log the entire body of the rest of the packet payload 504 * as the raw data of the frame. 505 */ 506 QLOG_BEGIN("raw"); 507 QLOG_BIN("data", PACKET_data(&orig_pkt), 508 PACKET_remaining(&orig_pkt)); 509 QLOG_END(); 510 ignore_res(PACKET_forward(pkt, PACKET_remaining(pkt))); 511 break; 512 } 513 514 return 1; 515 } 516 517 static void log_frame(QLOG *qlog_instance, PACKET *pkt, 518 size_t *need_skip) 519 { 520 size_t rem_before, rem_after; 521 522 rem_before = PACKET_remaining(pkt); 523 524 if (!log_frame_actual(qlog_instance, pkt, need_skip)) 525 return; 526 527 rem_after = PACKET_remaining(pkt); 528 QLOG_U64("length", rem_before - rem_after); 529 } 530 531 static int log_frames(QLOG *qlog_instance, 532 const OSSL_QTX_IOVEC *iovec, 533 size_t num_iovec) 534 { 535 size_t i; 536 PACKET pkt; 537 size_t need_skip = 0; 538 539 for (i = 0; i < num_iovec; ++i) { 540 if (!PACKET_buf_init(&pkt, iovec[i].buf, iovec[i].buf_len)) 541 return 0; 542 543 while (PACKET_remaining(&pkt) > 0) { 544 if (need_skip > 0) { 545 size_t adv = need_skip; 546 547 if (adv > PACKET_remaining(&pkt)) 548 adv = PACKET_remaining(&pkt); 549 550 if (!PACKET_forward(&pkt, adv)) 551 return 0; 552 553 need_skip -= adv; 554 continue; 555 } 556 557 QLOG_BEGIN(NULL) 558 { 559 log_frame(qlog_instance, &pkt, &need_skip); 560 } 561 QLOG_END() 562 } 563 } 564 565 return 1; 566 } 567 568 static void log_packet(QLOG *qlog_instance, 569 const QUIC_PKT_HDR *hdr, 570 QUIC_PN pn, 571 const OSSL_QTX_IOVEC *iovec, 572 size_t num_iovec, 573 uint64_t datagram_id) 574 { 575 const char *type_s; 576 577 QLOG_BEGIN("header") 578 type_s = quic_pkt_type_to_qlog(hdr->type); 579 if (type_s == NULL) 580 type_s = "unknown"; 581 582 QLOG_STR("packet_type", type_s); 583 if (ossl_quic_pkt_type_has_pn(hdr->type)) 584 QLOG_U64("packet_number", pn); 585 586 QLOG_CID("dcid", &hdr->dst_conn_id); 587 if (ossl_quic_pkt_type_has_scid(hdr->type)) 588 QLOG_CID("scid", &hdr->src_conn_id); 589 590 if (hdr->token_len > 0) { 591 QLOG_BEGIN("token") 592 QLOG_BEGIN("raw") 593 QLOG_BIN("data", hdr->token, hdr->token_len); 594 QLOG_END() 595 QLOG_END() 596 } 597 /* TODO(QLOG FUTURE): flags, length */ 598 QLOG_END() 599 QLOG_U64("datagram_id", datagram_id); 600 601 if (ossl_quic_pkt_type_is_encrypted(hdr->type)) { 602 QLOG_BEGIN_ARRAY("frames") 603 log_frames(qlog_instance, iovec, num_iovec); 604 QLOG_END_ARRAY() 605 } 606 } 607 608 #endif 609 610 void ossl_qlog_event_transport_packet_sent(QLOG *qlog, 611 const QUIC_PKT_HDR *hdr, 612 QUIC_PN pn, 613 const OSSL_QTX_IOVEC *iovec, 614 size_t num_iovec, 615 uint64_t datagram_id) 616 { 617 #ifndef OPENSSL_NO_QLOG 618 QLOG_EVENT_BEGIN(qlog, transport, packet_sent) 619 log_packet(qlog, hdr, pn, iovec, num_iovec, datagram_id); 620 QLOG_EVENT_END() 621 #endif 622 } 623 624 void ossl_qlog_event_transport_packet_received(QLOG *qlog, 625 const QUIC_PKT_HDR *hdr, 626 QUIC_PN pn, 627 const OSSL_QTX_IOVEC *iovec, 628 size_t num_iovec, 629 uint64_t datagram_id) 630 { 631 #ifndef OPENSSL_NO_QLOG 632 QLOG_EVENT_BEGIN(qlog, transport, packet_received) 633 log_packet(qlog, hdr, pn, iovec, num_iovec, datagram_id); 634 QLOG_EVENT_END() 635 #endif 636 } 637