]> git.proxmox.com Git - mirror_frr.git/blob - pceplib/pcep_session_logic.c
doc: Add `show ipv6 rpf X:X::X:X` command to docs
[mirror_frr.git] / pceplib / pcep_session_logic.c
1 /*
2 * This file is part of the PCEPlib, a PCEP protocol library.
3 *
4 * Copyright (C) 2020 Volta Networks https://voltanet.io/
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library 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 GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 *
19 * Author : Brady Johnson <brady@voltanet.io>
20 *
21 */
22
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27
28 #include <errno.h>
29 #include <limits.h>
30 #include <pthread.h>
31 #include <stdbool.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <time.h>
35
36 #include "pcep_msg_encoding.h"
37 #include "pcep_session_logic.h"
38 #include "pcep_session_logic_internals.h"
39 #include "pcep_timers.h"
40 #include "pcep_utils_counters.h"
41 #include "pcep_utils_ordered_list.h"
42 #include "pcep_utils_logging.h"
43 #include "pcep_utils_memory.h"
44
45 /*
46 * public API function implementations for the session_logic
47 */
48
49 pcep_session_logic_handle *session_logic_handle_ = NULL;
50 pcep_event_queue *session_logic_event_queue_ = NULL;
51 int session_id_ = 0;
52
53 void send_pcep_open(pcep_session *session); /* forward decl */
54
55 static bool run_session_logic_common(void)
56 {
57 if (session_logic_handle_ != NULL) {
58 pcep_log(LOG_WARNING,
59 "%s: Session Logic is already initialized.", __func__);
60 return false;
61 }
62
63 session_logic_handle_ = pceplib_malloc(
64 PCEPLIB_INFRA, sizeof(pcep_session_logic_handle));
65 memset(session_logic_handle_, 0, sizeof(pcep_session_logic_handle));
66
67 session_logic_handle_->active = true;
68 session_logic_handle_->session_list =
69 ordered_list_initialize(pointer_compare_function);
70 session_logic_handle_->session_event_queue = queue_initialize();
71
72 /* Initialize the event queue */
73 session_logic_event_queue_ =
74 pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_event_queue));
75 session_logic_event_queue_->event_queue = queue_initialize();
76 if (pthread_mutex_init(&(session_logic_event_queue_->event_queue_mutex),
77 NULL)
78 != 0) {
79 pcep_log(
80 LOG_ERR,
81 "%s: Cannot initialize session_logic event queue mutex.",
82 __func__);
83 return false;
84 }
85
86 pthread_cond_init(&(session_logic_handle_->session_logic_cond_var),
87 NULL);
88
89 if (pthread_mutex_init(&(session_logic_handle_->session_logic_mutex),
90 NULL)
91 != 0) {
92 pcep_log(LOG_ERR, "%s: Cannot initialize session_logic mutex.",
93 __func__);
94 return false;
95 }
96
97 pthread_mutex_lock(&(session_logic_handle_->session_logic_mutex));
98 session_logic_handle_->session_logic_condition = true;
99 pthread_cond_signal(&(session_logic_handle_->session_logic_cond_var));
100 pthread_mutex_unlock(&(session_logic_handle_->session_logic_mutex));
101
102 if (pthread_mutex_init(&(session_logic_handle_->session_list_mutex),
103 NULL)
104 != 0) {
105 pcep_log(LOG_ERR, "%s: Cannot initialize session_list mutex.",
106 __func__);
107 return false;
108 }
109
110 return true;
111 }
112
113
114 bool run_session_logic(void)
115 {
116 if (!run_session_logic_common()) {
117 return false;
118 }
119
120 if (pthread_create(&(session_logic_handle_->session_logic_thread), NULL,
121 session_logic_loop, session_logic_handle_)) {
122 pcep_log(LOG_ERR, "%s: Cannot initialize session_logic thread.",
123 __func__);
124 return false;
125 }
126
127 if (!initialize_timers(session_logic_timer_expire_handler)) {
128 pcep_log(LOG_ERR, "%s: Cannot initialize session_logic timers.",
129 __func__);
130 return false;
131 }
132
133 /* No need to call initialize_socket_comm_loop() since it will be
134 * called internally when the first socket_comm_session is created. */
135
136 return true;
137 }
138
139
140 bool run_session_logic_with_infra(pceplib_infra_config *infra_config)
141 {
142 if (infra_config == NULL) {
143 return run_session_logic();
144 }
145
146 /* Initialize the memory infrastructure before anything gets allocated
147 */
148 if (infra_config->pceplib_infra_mt != NULL
149 && infra_config->pceplib_messages_mt != NULL) {
150 pceplib_memory_initialize(
151 infra_config->pceplib_infra_mt,
152 infra_config->pceplib_messages_mt,
153 infra_config->malloc_func, infra_config->calloc_func,
154 infra_config->realloc_func, infra_config->strdup_func,
155 infra_config->free_func);
156 }
157
158 if (!run_session_logic_common()) {
159 return false;
160 }
161
162 /* Create the pcep_session_logic pthread so it can be managed externally
163 */
164 if (infra_config->pthread_create_func != NULL) {
165 if (infra_config->pthread_create_func(
166 &(session_logic_handle_->session_logic_thread),
167 NULL, session_logic_loop, session_logic_handle_,
168 "pcep_session_logic")) {
169 pcep_log(
170 LOG_ERR,
171 "%s: Cannot initialize external session_logic thread.",
172 __func__);
173 return false;
174 }
175 } else {
176 if (pthread_create(
177 &(session_logic_handle_->session_logic_thread),
178 NULL, session_logic_loop, session_logic_handle_)) {
179 pcep_log(LOG_ERR,
180 "%s: Cannot initialize session_logic thread.",
181 __func__);
182 return false;
183 }
184 }
185
186 session_logic_event_queue_->event_callback =
187 infra_config->pcep_event_func;
188 session_logic_event_queue_->event_callback_data =
189 infra_config->external_infra_data;
190
191 if (!initialize_timers_external_infra(
192 session_logic_timer_expire_handler,
193 infra_config->external_infra_data,
194 infra_config->timer_create_func,
195 infra_config->timer_cancel_func,
196 infra_config->pthread_create_func)) {
197 pcep_log(
198 LOG_ERR,
199 "%s: Cannot initialize session_logic timers with infra.",
200 __func__);
201 return false;
202 }
203
204 /* We found a problem with the FRR sockets, where not all the KeepAlive
205 * messages were received, so if the pthread_create_func is set, the
206 * internal PCEPlib socket infrastructure will be used. */
207
208 /* For the SocketComm, the socket_read/write_func and the
209 * pthread_create_func are mutually exclusive. */
210 if (infra_config->pthread_create_func != NULL) {
211 if (!initialize_socket_comm_external_infra(
212 infra_config->external_infra_data, NULL, NULL,
213 infra_config->pthread_create_func)) {
214 pcep_log(
215 LOG_ERR,
216 "%s: Cannot initialize session_logic socket comm with infra.",
217 __func__);
218 return false;
219 }
220 } else if (infra_config->socket_read_func != NULL
221 && infra_config->socket_write_func != NULL) {
222 if (!initialize_socket_comm_external_infra(
223 infra_config->external_infra_data,
224 infra_config->socket_read_func,
225 infra_config->socket_write_func, NULL)) {
226 pcep_log(
227 LOG_ERR,
228 "%s: Cannot initialize session_logic socket comm with infra.",
229 __func__);
230 return false;
231 }
232 }
233
234 return true;
235 }
236
237 bool run_session_logic_wait_for_completion(void)
238 {
239 if (!run_session_logic()) {
240 return false;
241 }
242
243 /* Blocking call, waits for session logic thread to complete */
244 pthread_join(session_logic_handle_->session_logic_thread, NULL);
245
246 return true;
247 }
248
249
250 bool stop_session_logic(void)
251 {
252 if (session_logic_handle_ == NULL) {
253 pcep_log(LOG_WARNING, "%s: Session logic already stopped",
254 __func__);
255 return false;
256 }
257
258 session_logic_handle_->active = false;
259 teardown_timers();
260
261 pthread_mutex_lock(&(session_logic_handle_->session_logic_mutex));
262 session_logic_handle_->session_logic_condition = true;
263 pthread_cond_signal(&(session_logic_handle_->session_logic_cond_var));
264 pthread_mutex_unlock(&(session_logic_handle_->session_logic_mutex));
265 pthread_join(session_logic_handle_->session_logic_thread, NULL);
266
267 pthread_mutex_destroy(&(session_logic_handle_->session_logic_mutex));
268 pthread_mutex_destroy(&(session_logic_handle_->session_list_mutex));
269 ordered_list_destroy(session_logic_handle_->session_list);
270 queue_destroy(session_logic_handle_->session_event_queue);
271
272 /* destroy the event_queue */
273 pthread_mutex_destroy(&(session_logic_event_queue_->event_queue_mutex));
274 queue_destroy(session_logic_event_queue_->event_queue);
275 pceplib_free(PCEPLIB_INFRA, session_logic_event_queue_);
276
277 /* Explicitly stop the socket comm loop started by the pcep_sessions */
278 destroy_socket_comm_loop();
279
280 pceplib_free(PCEPLIB_INFRA, session_logic_handle_);
281 session_logic_handle_ = NULL;
282
283 return true;
284 }
285
286
287 void close_pcep_session(pcep_session *session)
288 {
289 close_pcep_session_with_reason(session, PCEP_CLOSE_REASON_NO);
290 }
291
292 void close_pcep_session_with_reason(pcep_session *session,
293 enum pcep_close_reason reason)
294 {
295 struct pcep_message *close_msg = pcep_msg_create_close(reason);
296
297 pcep_log(
298 LOG_INFO,
299 "%s: [%ld-%ld] pcep_session_logic send pcep_close message for session [%d]",
300 __func__, time(NULL), pthread_self(), session->session_id);
301
302 session_send_message(session, close_msg);
303 socket_comm_session_close_tcp_after_write(session->socket_comm_session);
304 session->session_state = SESSION_STATE_INITIALIZED;
305 }
306
307
308 void destroy_pcep_session(pcep_session *session)
309 {
310 if (session == NULL) {
311 pcep_log(LOG_WARNING, "%s: Cannot destroy NULL session",
312 __func__);
313 return;
314 }
315
316 /* Remove the session from the session_list and synchronize session
317 * destroy with the session_logic_loop, so that no in-flight events
318 * will be handled now that the session is destroyed. */
319 pthread_mutex_lock(&(session_logic_handle_->session_list_mutex));
320 ordered_list_remove_first_node_equals(
321 session_logic_handle_->session_list, session);
322 pcep_log(LOG_DEBUG,
323 "%s: destroy_pcep_session delete session_list sessionPtr %p",
324 __func__, session);
325
326 pcep_session_cancel_timers(session);
327 delete_counters_group(session->pcep_session_counters);
328 queue_destroy_with_data(session->num_unknown_messages_time_queue);
329 socket_comm_session_teardown(session->socket_comm_session);
330
331 if (session->pcc_config.pcep_msg_versioning != NULL) {
332 pceplib_free(PCEPLIB_INFRA,
333 session->pcc_config.pcep_msg_versioning);
334 }
335
336 if (session->pce_config.pcep_msg_versioning != NULL) {
337 pceplib_free(PCEPLIB_INFRA,
338 session->pce_config.pcep_msg_versioning);
339 }
340
341 int session_id = session->session_id;
342 pceplib_free(PCEPLIB_INFRA, session);
343 pcep_log(LOG_INFO, "%s: [%ld-%ld] session [%d] destroyed", __func__,
344 time(NULL), pthread_self(), session_id);
345 pthread_mutex_unlock(&(session_logic_handle_->session_list_mutex));
346 }
347
348 void pcep_session_cancel_timers(pcep_session *session)
349 {
350 if (session == NULL) {
351 return;
352 }
353
354 if (session->timer_id_dead_timer != TIMER_ID_NOT_SET) {
355 cancel_timer(session->timer_id_dead_timer);
356 }
357
358 if (session->timer_id_keep_alive != TIMER_ID_NOT_SET) {
359 cancel_timer(session->timer_id_keep_alive);
360 }
361
362 if (session->timer_id_open_keep_wait != TIMER_ID_NOT_SET) {
363 cancel_timer(session->timer_id_open_keep_wait);
364 }
365
366 if (session->timer_id_open_keep_alive != TIMER_ID_NOT_SET) {
367 cancel_timer(session->timer_id_open_keep_alive);
368 }
369 }
370
371 /* Internal util function */
372 static int get_next_session_id(void)
373 {
374 if (session_id_ == INT_MAX) {
375 session_id_ = 0;
376 }
377
378 return session_id_++;
379 }
380
381 /* Internal util function */
382 static pcep_session *create_pcep_session_pre_setup(pcep_configuration *config)
383 {
384 if (config == NULL) {
385 pcep_log(LOG_WARNING,
386 "%s: Cannot create pcep session with NULL config",
387 __func__);
388 return NULL;
389 }
390
391 pcep_session *session =
392 pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_session));
393 memset(session, 0, sizeof(pcep_session));
394 session->session_id = get_next_session_id();
395 session->session_state = SESSION_STATE_INITIALIZED;
396 session->timer_id_open_keep_wait = TIMER_ID_NOT_SET;
397 session->timer_id_open_keep_alive = TIMER_ID_NOT_SET;
398 session->timer_id_dead_timer = TIMER_ID_NOT_SET;
399 session->timer_id_keep_alive = TIMER_ID_NOT_SET;
400 session->stateful_pce = false;
401 session->num_unknown_messages_time_queue = queue_initialize();
402 session->pce_open_received = false;
403 session->pce_open_rejected = false;
404 session->pce_open_keep_alive_sent = false;
405 session->pcc_open_rejected = false;
406 session->pce_open_accepted = false;
407 session->pcc_open_accepted = false;
408 session->destroy_session_after_write = false;
409 session->lsp_db_version = config->lsp_db_version;
410 memcpy(&(session->pcc_config), config, sizeof(pcep_configuration));
411 /* copy the pcc_config to the pce_config until we receive the open
412 * keep_alive response */
413 memcpy(&(session->pce_config), config, sizeof(pcep_configuration));
414 if (config->pcep_msg_versioning != NULL) {
415 session->pcc_config.pcep_msg_versioning = pceplib_malloc(
416 PCEPLIB_INFRA, sizeof(struct pcep_versioning));
417 memcpy(session->pcc_config.pcep_msg_versioning,
418 config->pcep_msg_versioning,
419 sizeof(struct pcep_versioning));
420 session->pce_config.pcep_msg_versioning = pceplib_malloc(
421 PCEPLIB_INFRA, sizeof(struct pcep_versioning));
422 memcpy(session->pce_config.pcep_msg_versioning,
423 config->pcep_msg_versioning,
424 sizeof(struct pcep_versioning));
425 }
426
427 pthread_mutex_lock(&(session_logic_handle_->session_list_mutex));
428 ordered_list_add_node(session_logic_handle_->session_list, session);
429 pcep_log(
430 LOG_DEBUG,
431 "%s: create_pcep_session_pre_setup add session_list sessionPtr %p",
432 __func__, session);
433 pthread_mutex_unlock(&(session_logic_handle_->session_list_mutex));
434
435 return session;
436 }
437
438 /* Internal util function */
439 static bool create_pcep_session_post_setup(pcep_session *session)
440 {
441 if (!socket_comm_session_connect_tcp(session->socket_comm_session)) {
442 pcep_log(LOG_WARNING, "%s: Cannot establish TCP socket.",
443 __func__);
444 destroy_pcep_session(session);
445
446 return false;
447 }
448
449 session->time_connected = time(NULL);
450 create_session_counters(session);
451
452 send_pcep_open(session);
453
454 session->session_state = SESSION_STATE_PCEP_CONNECTING;
455 session->timer_id_open_keep_wait =
456 create_timer(session->pcc_config.keep_alive_seconds, session);
457 // session->session_state = SESSION_STATE_OPENED;
458
459 return true;
460 }
461
462 pcep_session *create_pcep_session(pcep_configuration *config,
463 struct in_addr *pce_ip)
464 {
465 if (pce_ip == NULL) {
466 pcep_log(LOG_WARNING,
467 "%s: Cannot create pcep session with NULL pce_ip",
468 __func__);
469 return NULL;
470 }
471
472 pcep_session *session = create_pcep_session_pre_setup(config);
473 if (session == NULL) {
474 return NULL;
475 }
476
477 session->socket_comm_session = socket_comm_session_initialize_with_src(
478 NULL, session_logic_msg_ready_handler,
479 session_logic_message_sent_handler,
480 session_logic_conn_except_notifier, &(config->src_ip.src_ipv4),
481 ((config->src_pcep_port == 0) ? PCEP_TCP_PORT
482 : config->src_pcep_port),
483 pce_ip,
484 ((config->dst_pcep_port == 0) ? PCEP_TCP_PORT
485 : config->dst_pcep_port),
486 config->socket_connect_timeout_millis,
487 config->tcp_authentication_str, config->is_tcp_auth_md5,
488 session);
489 if (session->socket_comm_session == NULL) {
490 pcep_log(LOG_WARNING,
491 "%s: Cannot establish socket_comm_session.", __func__);
492 destroy_pcep_session(session);
493
494 return NULL;
495 }
496
497 if (create_pcep_session_post_setup(session) == false) {
498 return NULL;
499 }
500
501 return session;
502 }
503
504 pcep_session *create_pcep_session_ipv6(pcep_configuration *config,
505 struct in6_addr *pce_ip)
506 {
507 if (pce_ip == NULL) {
508 pcep_log(LOG_WARNING,
509 "%s: Cannot create pcep session with NULL pce_ip",
510 __func__);
511 return NULL;
512 }
513
514 pcep_session *session = create_pcep_session_pre_setup(config);
515 if (session == NULL) {
516 return NULL;
517 }
518
519 session->socket_comm_session =
520 socket_comm_session_initialize_with_src_ipv6(
521 NULL, session_logic_msg_ready_handler,
522 session_logic_message_sent_handler,
523 session_logic_conn_except_notifier,
524 &(config->src_ip.src_ipv6),
525 ((config->src_pcep_port == 0) ? PCEP_TCP_PORT
526 : config->src_pcep_port),
527 pce_ip,
528 ((config->dst_pcep_port == 0) ? PCEP_TCP_PORT
529 : config->dst_pcep_port),
530 config->socket_connect_timeout_millis,
531 config->tcp_authentication_str, config->is_tcp_auth_md5,
532 session);
533 if (session->socket_comm_session == NULL) {
534 pcep_log(LOG_WARNING,
535 "%s: Cannot establish ipv6 socket_comm_session.",
536 __func__);
537 destroy_pcep_session(session);
538
539 return NULL;
540 }
541
542 if (create_pcep_session_post_setup(session) == false) {
543 return NULL;
544 }
545
546 return session;
547 }
548
549
550 void session_send_message(pcep_session *session, struct pcep_message *message)
551 {
552 pcep_encode_message(message, session->pcc_config.pcep_msg_versioning);
553 socket_comm_session_send_message(session->socket_comm_session,
554 (char *)message->encoded_message,
555 message->encoded_message_length, true);
556
557 increment_message_tx_counters(session, message);
558
559 /* The message->encoded_message will be freed in
560 * socket_comm_session_send_message() once sent.
561 * Setting to NULL here so pcep_msg_free_message() does not free it */
562 message->encoded_message = NULL;
563 pcep_msg_free_message(message);
564 }
565
566
567 /* This function is also used in pcep_session_logic_states.c */
568 struct pcep_message *create_pcep_open(pcep_session *session)
569 {
570 /* create and send PCEP open
571 * with PCEP, the PCC sends the config the PCE should use in the open
572 * message,
573 * and the PCE will send an open with the config the PCC should use. */
574 double_linked_list *tlv_list = dll_initialize();
575 if (session->pcc_config.support_stateful_pce_lsp_update
576 || session->pcc_config.support_pce_lsp_instantiation
577 || session->pcc_config.support_include_db_version
578 || session->pcc_config.support_lsp_triggered_resync
579 || session->pcc_config.support_lsp_delta_sync
580 || session->pcc_config.support_pce_triggered_initial_sync) {
581 /* Prepend this TLV as the first in the list */
582 dll_append(
583 tlv_list,
584 pcep_tlv_create_stateful_pce_capability(
585 /* U flag */
586 session->pcc_config
587 .support_stateful_pce_lsp_update,
588 /* S flag */
589 session->pcc_config.support_include_db_version,
590 /* I flag */
591 session->pcc_config
592 .support_pce_lsp_instantiation,
593 /* T flag */
594 session->pcc_config
595 .support_lsp_triggered_resync,
596 /* D flag */
597 session->pcc_config.support_lsp_delta_sync,
598 /* F flag */
599 session->pcc_config
600 .support_pce_triggered_initial_sync));
601 }
602
603 if (session->pcc_config.support_include_db_version) {
604 if (session->pcc_config.lsp_db_version != 0) {
605 dll_append(tlv_list,
606 pcep_tlv_create_lsp_db_version(
607 session->pcc_config.lsp_db_version));
608 }
609 }
610
611 if (session->pcc_config.support_sr_te_pst) {
612 bool flag_n = false;
613 bool flag_x = false;
614 if (session->pcc_config.pcep_msg_versioning
615 ->draft_ietf_pce_segment_routing_07
616 == false) {
617 flag_n = session->pcc_config.pcc_can_resolve_nai_to_sid;
618 flag_x = (session->pcc_config.max_sid_depth == 0);
619 }
620
621 struct pcep_object_tlv_sr_pce_capability *sr_pce_cap_tlv =
622 pcep_tlv_create_sr_pce_capability(
623 flag_n, flag_x,
624 session->pcc_config.max_sid_depth);
625
626 double_linked_list *sub_tlv_list = NULL;
627 if (session->pcc_config.pcep_msg_versioning
628 ->draft_ietf_pce_segment_routing_07
629 == true) {
630 /* With draft07, send the sr_pce_cap_tlv as a normal TLV
631 */
632 dll_append(tlv_list, sr_pce_cap_tlv);
633 } else {
634 /* With draft16, send the sr_pce_cap_tlv as a sub-TLV in
635 * the path_setup_type_capability TLV */
636 sub_tlv_list = dll_initialize();
637 dll_append(sub_tlv_list, sr_pce_cap_tlv);
638 }
639
640 uint8_t *pst =
641 pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint8_t));
642 *pst = SR_TE_PST;
643 double_linked_list *pst_list = dll_initialize();
644 dll_append(pst_list, pst);
645 dll_append(tlv_list, pcep_tlv_create_path_setup_type_capability(
646 pst_list, sub_tlv_list));
647 }
648
649 struct pcep_message *open_msg = pcep_msg_create_open_with_tlvs(
650 session->pcc_config.keep_alive_seconds,
651 session->pcc_config.dead_timer_seconds, session->session_id,
652 tlv_list);
653
654 pcep_log(
655 LOG_INFO,
656 "%s: [%ld-%ld] pcep_session_logic create open message: TLVs [%d] for session [%d]",
657 __func__, time(NULL), pthread_self(), tlv_list->num_entries,
658 session->session_id);
659
660 return (open_msg);
661 }
662
663
664 void send_pcep_open(pcep_session *session)
665 {
666 session_send_message(session, create_pcep_open(session));
667 }
668
669 /* This is a blocking call, since it is synchronized with destroy_pcep_session()
670 * and session_logic_loop(). It may be possible that the session has been
671 * deleted but API users havent been informed yet.
672 */
673 bool session_exists(pcep_session *session)
674 {
675 if (session_logic_handle_ == NULL) {
676 pcep_log(LOG_DEBUG,
677 "%s: session_exists session_logic_handle_ is NULL",
678 __func__);
679 return false;
680 }
681
682 pthread_mutex_lock(&(session_logic_handle_->session_list_mutex));
683 bool retval =
684 (ordered_list_find(session_logic_handle_->session_list, session)
685 != NULL);
686 pthread_mutex_unlock(&(session_logic_handle_->session_list_mutex));
687
688 return retval;
689 }