1 /*- 2 * Copyright (c) 2007 Robert N. M. Watson 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 * 26 * $FreeBSD$ 27 */ 28 29 /* 30 * A few regression tests for UNIX domain sockets. Run from single-user mode 31 * as it checks the openfiles sysctl to look for leaks, and we don't want that 32 * changing due to other processes doing stuff. 33 */ 34 35 #include <sys/types.h> 36 #include <sys/signal.h> 37 #include <sys/socket.h> 38 #include <sys/sysctl.h> 39 #include <sys/un.h> 40 #include <sys/wait.h> 41 42 #include <netinet/in.h> 43 44 #include <err.h> 45 #include <errno.h> 46 #include <fcntl.h> 47 #include <limits.h> 48 #include <stdio.h> 49 #include <stdlib.h> 50 #include <string.h> 51 #include <unistd.h> 52 53 static int forcegc = 1; 54 static char dpath[PATH_MAX]; 55 static const char *test; 56 57 static int 58 getopenfiles(void) 59 { 60 size_t len; 61 int i; 62 63 len = sizeof(i); 64 if (sysctlbyname("kern.openfiles", &i, &len, NULL, 0) < 0) 65 err(-1, "kern.openfiles"); 66 return (i); 67 } 68 69 static int 70 getinflight(void) 71 { 72 size_t len; 73 int i; 74 75 len = sizeof(i); 76 if (sysctlbyname("net.local.inflight", &i, &len, NULL, 0) < 0) 77 err(-1, "net.local.inflight"); 78 return (i); 79 } 80 81 static void 82 sendfd(int fd, int fdtosend) 83 { 84 struct msghdr mh; 85 struct message { struct cmsghdr msg_hdr; int fd; } m; 86 ssize_t len; 87 int after_inflight, before_inflight; 88 89 before_inflight = getinflight(); 90 91 bzero(&mh, sizeof(mh)); 92 bzero(&m, sizeof(m)); 93 mh.msg_control = &m; 94 mh.msg_controllen = sizeof(m); 95 m.msg_hdr.cmsg_len = sizeof(m); 96 m.msg_hdr.cmsg_level = SOL_SOCKET; 97 m.msg_hdr.cmsg_type = SCM_RIGHTS; 98 m.fd = fdtosend; 99 len = sendmsg(fd, &mh, 0); 100 if (len < 0) 101 err(-1, "%s: sendmsg", test); 102 after_inflight = getinflight(); 103 if (after_inflight != before_inflight + 1) 104 errx(-1, "%s: sendfd: before %d after %d\n", test, 105 before_inflight, after_inflight); 106 } 107 108 static void 109 close2(int fd1, int fd2) 110 { 111 112 close(fd1); 113 close(fd2); 114 } 115 116 static void 117 close3(int fd1, int fd2, int fd3) 118 { 119 120 close2(fd1, fd2); 121 close(fd3); 122 } 123 124 static void 125 close4(int fd1, int fd2, int fd3, int fd4) 126 { 127 128 close2(fd1, fd2); 129 close2(fd3, fd4); 130 } 131 132 static void 133 close5(int fd1, int fd2, int fd3, int fd4, int fd5) 134 { 135 136 close3(fd1, fd2, fd3); 137 close2(fd4, fd5); 138 } 139 140 static int 141 my_socket(int domain, int type, int proto) 142 { 143 int sock; 144 145 sock = socket(domain, type, proto); 146 if (sock < 0) 147 err(-1, "%s: socket", test); 148 return (sock); 149 } 150 151 static void 152 my_bind(int sock, struct sockaddr *sa, socklen_t len) 153 { 154 155 if (bind(sock, sa, len) < 0) 156 err(-1, "%s: bind", test); 157 } 158 159 static void 160 my_connect(int sock, struct sockaddr *sa, socklen_t len) 161 { 162 163 if (connect(sock, sa, len) < 0) 164 err(-1, "%s: connect", test); 165 } 166 167 static void 168 my_listen(int sock, int backlog) 169 { 170 171 if (listen(sock, backlog) < 0) 172 err(-1, "%s: listen", test); 173 } 174 175 static void 176 my_socketpair(int *sv) 177 { 178 179 if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) < 0) 180 err(-1, "%s: socketpair", test); 181 } 182 183 static void 184 my_getsockname(int s, struct sockaddr *sa, socklen_t *salen) 185 { 186 187 if (getsockname(s, sa, salen) < 0) 188 err(-1, "%s: getsockname", test); 189 } 190 191 static void 192 setnonblock(int s) 193 { 194 195 if (fcntl(s, F_SETFL, O_NONBLOCK) < 0) 196 err(-1, "%s: fcntl(F_SETFL, O_NONBLOCK)", test); 197 } 198 199 static void 200 alloc3fds(int *s, int *sv) 201 { 202 203 if ((*s = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) 204 err(-1, "%s: socket", test); 205 if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) < 0) 206 err(-1, "%s: socketpair", test); 207 } 208 209 static void 210 alloc5fds(int *s, int *sva, int *svb) 211 { 212 213 if ((*s = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) 214 err(-1, "%s: socket", test); 215 if (socketpair(PF_UNIX, SOCK_STREAM, 0, sva) < 0) 216 err(-1, "%s: socketpair", test); 217 if (socketpair(PF_UNIX, SOCK_STREAM, 0, svb) < 0) 218 err(-1, "%s: socketpair", test); 219 } 220 221 static void 222 save_sysctls(int *before_inflight, int *before_openfiles) 223 { 224 225 *before_inflight = getinflight(); 226 *before_openfiles = getopenfiles(); 227 } 228 229 /* 230 * Try hard to make sure that the GC does in fact run before we test the 231 * condition of things. 232 */ 233 static void 234 trigger_gc(void) 235 { 236 int s; 237 238 if (forcegc) { 239 if ((s = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) 240 err(-1, "trigger_gc: socket"); 241 close(s); 242 } 243 sleep(1); 244 } 245 246 static void 247 test_sysctls(int before_inflight, int before_openfiles) 248 { 249 int after_inflight, after_openfiles; 250 251 trigger_gc(); 252 after_inflight = getinflight(); 253 if (after_inflight != before_inflight) 254 warnx("%s: before inflight: %d, after inflight: %d", 255 test, before_inflight, after_inflight); 256 257 after_openfiles = getopenfiles(); 258 if (after_openfiles != before_openfiles) 259 warnx("%s: before: %d, after: %d", test, before_openfiles, 260 after_openfiles); 261 } 262 263 static void 264 twosome_nothing(void) 265 { 266 int inflight, openfiles; 267 int sv[2]; 268 269 /* 270 * Create a pair, close in one order. 271 */ 272 test = "twosome_nothing1"; 273 printf("%s\n", test); 274 save_sysctls(&inflight, &openfiles); 275 my_socketpair(sv); 276 close2(sv[0], sv[1]); 277 test_sysctls(inflight, openfiles); 278 279 /* 280 * Create a pair, close in the other order. 281 */ 282 test = "twosome_nothing2"; 283 printf("%s\n", test); 284 save_sysctls(&inflight, &openfiles); 285 my_socketpair(sv); 286 close2(sv[0], sv[1]); 287 test_sysctls(inflight, openfiles); 288 } 289 290 /* 291 * Using a socket pair, send various endpoints over the pair and close in 292 * various orders. 293 */ 294 static void 295 twosome_drop_work(const char *testname, int sendvia, int tosend, int closefirst) 296 { 297 int inflight, openfiles; 298 int sv[2]; 299 300 printf("%s\n", testname); 301 test = testname; 302 save_sysctls(&inflight, &openfiles); 303 my_socketpair(sv); 304 sendfd(sv[sendvia], sv[tosend]); 305 if (closefirst == 0) 306 close2(sv[0], sv[1]); 307 else 308 close2(sv[1], sv[0]); 309 test_sysctls(inflight, openfiles); 310 } 311 312 static void 313 twosome_drop(void) 314 { 315 316 /* 317 * In various combations, some wastefully symmetric, create socket 318 * pairs and send one or another endpoint over one or another 319 * endpoint, closing the endpoints in various orders. 320 */ 321 twosome_drop_work("twosome_drop1", 0, 0, 0); 322 twosome_drop_work("twosome_drop2", 0, 0, 1); 323 twosome_drop_work("twosome_drop3", 0, 1, 0); 324 twosome_drop_work("twosome_drop4", 0, 1, 1); 325 twosome_drop_work("twosome_drop5", 1, 0, 0); 326 twosome_drop_work("twosome_drop6", 1, 0, 1); 327 twosome_drop_work("twosome_drop7", 1, 1, 0); 328 twosome_drop_work("twosome_drop8", 1, 1, 1); 329 } 330 331 static void 332 threesome_nothing(void) 333 { 334 int inflight, openfiles; 335 int s, sv[2]; 336 337 test = "threesome_nothing"; 338 printf("%s\n", test); 339 save_sysctls(&inflight, &openfiles); 340 alloc3fds(&s, sv); 341 close3(s, sv[0], sv[1]); 342 test_sysctls(inflight, openfiles); 343 } 344 345 /* 346 * threesome_drop: create a pair and a spare, send the spare over the pair, and 347 * close in various orders and make sure all the fds went away. 348 */ 349 static void 350 threesome_drop(void) 351 { 352 int inflight, openfiles; 353 int s, sv[2]; 354 355 /* 356 * threesome_drop1: close sent send receive 357 */ 358 test = "threesome_drop1"; 359 printf("%s\n", test); 360 save_sysctls(&inflight, &openfiles); 361 alloc3fds(&s, sv); 362 sendfd(sv[0], s); 363 close3(s, sv[0], sv[1]); 364 test_sysctls(inflight, openfiles); 365 366 /* 367 * threesome_drop2: close sent receive send 368 */ 369 test = "threesome_drop2"; 370 printf("%s\n", test); 371 save_sysctls(&inflight, &openfiles); 372 alloc3fds(&s, sv); 373 sendfd(sv[0], s); 374 close3(s, sv[1], sv[0]); 375 test_sysctls(inflight, openfiles); 376 377 /* 378 * threesome_drop3: close receive sent send 379 */ 380 test = "threesome_drop3"; 381 printf("%s\n", test); 382 save_sysctls(&inflight, &openfiles); 383 alloc3fds(&s, sv); 384 sendfd(sv[0], s); 385 close3(sv[1], s, sv[0]); 386 test_sysctls(inflight, openfiles); 387 388 /* 389 * threesome_drop4: close receive send sent 390 */ 391 test = "threesome_drop4"; 392 printf("%s\n", test); 393 save_sysctls(&inflight, &openfiles); 394 alloc3fds(&s, sv); 395 sendfd(sv[0], s); 396 close3(sv[1], sv[0], s); 397 test_sysctls(inflight, openfiles); 398 399 /* 400 * threesome_drop5: close send receive sent 401 */ 402 test = "threesome_drop5"; 403 printf("%s\n", test); 404 save_sysctls(&inflight, &openfiles); 405 alloc3fds(&s, sv); 406 sendfd(sv[0], s); 407 close3(sv[0], sv[1], s); 408 test_sysctls(inflight, openfiles); 409 410 /* 411 * threesome_drop6: close send sent receive 412 */ 413 test = "threesome_drop6"; 414 printf("%s\n", test); 415 save_sysctls(&inflight, &openfiles); 416 alloc3fds(&s, sv); 417 close3(sv[0], s, sv[1]); 418 test_sysctls(inflight, openfiles); 419 } 420 421 /* 422 * Fivesome tests: create two socket pairs and a spare, send the spare over 423 * the first socket pair, then send the first socket pair over the second 424 * socket pair, and GC. Do various closes at various points to exercise 425 * various cases. 426 */ 427 static void 428 fivesome_nothing(void) 429 { 430 int inflight, openfiles; 431 int spare, sva[2], svb[2]; 432 433 test = "fivesome_nothing"; 434 printf("%s\n", test); 435 save_sysctls(&inflight, &openfiles); 436 alloc5fds(&spare, sva, svb); 437 close5(spare, sva[0], sva[1], svb[0], svb[1]); 438 test_sysctls(inflight, openfiles); 439 } 440 441 static void 442 fivesome_drop_work(const char *testname, int close_spare_after_send, 443 int close_sva_after_send) 444 { 445 int inflight, openfiles; 446 int spare, sva[2], svb[2]; 447 448 printf("%s\n", testname); 449 test = testname; 450 save_sysctls(&inflight, &openfiles); 451 alloc5fds(&spare, sva, svb); 452 453 /* 454 * Send spare over sva. 455 */ 456 sendfd(sva[0], spare); 457 if (close_spare_after_send) 458 close(spare); 459 460 /* 461 * Send sva over svb. 462 */ 463 sendfd(svb[0], sva[0]); 464 sendfd(svb[0], sva[1]); 465 if (close_sva_after_send) 466 close2(sva[0], sva[1]); 467 468 close2(svb[0], svb[1]); 469 470 if (!close_sva_after_send) 471 close2(sva[0], sva[1]); 472 if (!close_spare_after_send) 473 close(spare); 474 475 test_sysctls(inflight, openfiles); 476 } 477 478 static void 479 fivesome_drop(void) 480 { 481 482 fivesome_drop_work("fivesome_drop1", 0, 0); 483 fivesome_drop_work("fivesome_drop2", 0, 1); 484 fivesome_drop_work("fivesome_drop3", 1, 0); 485 fivesome_drop_work("fivesome_drop4", 1, 1); 486 } 487 488 /* 489 * Create a somewhat nasty dual-socket socket intended to upset the garbage 490 * collector if mark-and-sweep is wrong. 491 */ 492 static void 493 complex_cycles(void) 494 { 495 int inflight, openfiles; 496 int spare, sva[2], svb[2]; 497 498 test = "complex_cycles"; 499 printf("%s\n", test); 500 save_sysctls(&inflight, &openfiles); 501 alloc5fds(&spare, sva, svb); 502 sendfd(sva[0], svb[0]); 503 sendfd(sva[0], svb[1]); 504 sendfd(svb[0], sva[0]); 505 sendfd(svb[0], sva[1]); 506 sendfd(svb[0], spare); 507 sendfd(sva[0], spare); 508 close5(spare, sva[0], sva[1], svb[0], svb[1]); 509 test_sysctls(inflight, openfiles); 510 } 511 512 /* 513 * Listen sockets can also be passed over UNIX domain sockets, so test 514 * various cases, including ones where listen sockets have waiting sockets 515 * hanging off them... 516 */ 517 static void 518 listen_nothing(void) 519 { 520 struct sockaddr_un sun; 521 struct sockaddr_in sin; 522 int inflight, openfiles; 523 int s; 524 525 test = "listen_nothing_unp"; 526 printf("%s\n", test); 527 bzero(&sun, sizeof(sun)); 528 sun.sun_family = AF_LOCAL; 529 sun.sun_len = sizeof(sun); 530 snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/%s", dpath, test); 531 save_sysctls(&inflight, &openfiles); 532 s = my_socket(PF_LOCAL, SOCK_STREAM, 0); 533 my_bind(s, (struct sockaddr *)&sun, sizeof(sun)); 534 my_listen(s, -1); 535 close(s); 536 (void)unlink(sun.sun_path); 537 test_sysctls(inflight, openfiles); 538 539 test = "listen_nothing_inet"; 540 printf("%s\n", test); 541 bzero(&sin, sizeof(sin)); 542 sin.sin_family = AF_INET; 543 sin.sin_len = sizeof(sin); 544 sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 545 sin.sin_port = htons(0); 546 save_sysctls(&inflight, &openfiles); 547 s = my_socket(PF_INET, SOCK_STREAM, 0); 548 my_bind(s, (struct sockaddr *)&sin, sizeof(sin)); 549 my_listen(s, -1); 550 close(s); 551 test_sysctls(inflight, openfiles); 552 } 553 554 /* 555 * Send a listen UDP socket over a UNIX domain socket. 556 * 557 * Send a listen TCP socket over a UNIX domain socket. 558 * 559 * Do each twice, with closing of the listen socket vs. socketpair in 560 * different orders. 561 */ 562 static void 563 listen_drop(void) 564 { 565 struct sockaddr_un sun; 566 struct sockaddr_in sin; 567 int inflight, openfiles; 568 int s, sv[2]; 569 570 bzero(&sun, sizeof(sun)); 571 sun.sun_family = AF_LOCAL; 572 sun.sun_len = sizeof(sun); 573 574 /* 575 * Close listen socket first. 576 */ 577 test = "listen_drop_unp1"; 578 printf("%s\n", test); 579 snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/%s", dpath, test); 580 save_sysctls(&inflight, &openfiles); 581 s = my_socket(PF_LOCAL, SOCK_STREAM, 0); 582 my_bind(s, (struct sockaddr *)&sun, sizeof(sun)); 583 my_listen(s, -1); 584 my_socketpair(sv); 585 sendfd(sv[0], s); 586 close3(s, sv[0], sv[1]); 587 test_sysctls(inflight, openfiles); 588 589 /* 590 * Close socketpair first. 591 */ 592 test = "listen_drop_unp2"; 593 printf("%s\n", test); 594 snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/%s", dpath, test); 595 save_sysctls(&inflight, &openfiles); 596 s = my_socket(PF_LOCAL, SOCK_STREAM, 0); 597 my_bind(s, (struct sockaddr *)&sun, sizeof(sun)); 598 my_listen(s, -1); 599 my_socketpair(sv); 600 sendfd(sv[0], s); 601 close3(sv[0], sv[1], s); 602 test_sysctls(inflight, openfiles); 603 604 sin.sin_family = AF_INET; 605 sin.sin_len = sizeof(sin); 606 sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 607 sin.sin_port = htons(0); 608 609 /* 610 * Close listen socket first. 611 */ 612 test = "listen_drop_inet1"; 613 printf("%s\n", test); 614 bzero(&sun, sizeof(sun)); 615 save_sysctls(&inflight, &openfiles); 616 s = my_socket(PF_INET, SOCK_STREAM, 0); 617 my_bind(s, (struct sockaddr *)&sin, sizeof(sin)); 618 my_listen(s, -1); 619 my_socketpair(sv); 620 sendfd(sv[0], s); 621 close3(s, sv[0], sv[1]); 622 test_sysctls(inflight, openfiles); 623 624 /* 625 * Close socketpair first. 626 */ 627 test = "listen_drop_inet2"; 628 printf("%s\n", test); 629 bzero(&sun, sizeof(sun)); 630 save_sysctls(&inflight, &openfiles); 631 s = my_socket(PF_INET, SOCK_STREAM, 0); 632 my_bind(s, (struct sockaddr *)&sin, sizeof(sin)); 633 my_listen(s, -1); 634 my_socketpair(sv); 635 sendfd(sv[0], s); 636 close3(sv[0], sv[1], s); 637 test_sysctls(inflight, openfiles); 638 } 639 640 /* 641 * Up things a notch with listen sockets: add connections that can be 642 * accepted to the listen queues. 643 */ 644 static void 645 listen_connect_nothing(void) 646 { 647 struct sockaddr_in sin; 648 int slisten, sconnect, sv[2]; 649 int inflight, openfiles; 650 socklen_t len; 651 652 test = "listen_connect_nothing"; 653 printf("%s\n", test); 654 save_sysctls(&inflight, &openfiles); 655 656 slisten = my_socket(PF_INET, SOCK_STREAM, 0); 657 my_bind(slisten, (struct sockaddr *)&sin, sizeof(sin)); 658 my_listen(slisten, -1); 659 660 my_socketpair(sv); 661 662 len = sizeof(sin); 663 my_getsockname(slisten, (struct sockaddr *)&sin, &len); 664 665 sconnect = my_socket(PF_INET, SOCK_STREAM, 0); 666 setnonblock(sconnect); 667 my_connect(sconnect, (struct sockaddr *)&sin, len); 668 669 sleep(1); 670 671 close4(slisten, sconnect, sv[0], sv[1]); 672 673 test_sysctls(inflight, openfiles); 674 } 675 676 static void 677 listen_connect_drop(void) 678 { 679 struct sockaddr_in sin; 680 int slisten, sconnect, sv[2]; 681 int inflight, openfiles; 682 socklen_t len; 683 684 test = "listen_connect_drop"; 685 printf("%s\n", test); 686 save_sysctls(&inflight, &openfiles); 687 688 slisten = my_socket(PF_INET, SOCK_STREAM, 0); 689 my_bind(slisten, (struct sockaddr *)&sin, sizeof(sin)); 690 my_listen(slisten, -1); 691 692 my_socketpair(sv); 693 694 len = sizeof(sin); 695 my_getsockname(slisten, (struct sockaddr *)&sin, &len); 696 697 sconnect = my_socket(PF_INET, SOCK_STREAM, 0); 698 setnonblock(sconnect); 699 my_connect(sconnect, (struct sockaddr *)&sin, len); 700 701 sleep(1); 702 sendfd(sv[0], slisten); 703 close3(slisten, sv[0], sv[1]); 704 sleep(1); 705 close(sconnect); 706 707 test_sysctls(inflight, openfiles); 708 } 709 710 #define RMDIR "rm -Rf " 711 int 712 main(int argc, char *argv[]) 713 { 714 char cmd[sizeof(RMDIR) + PATH_MAX]; 715 int serrno; 716 pid_t pid; 717 718 strlcpy(dpath, "/tmp/unpgc.XXXXXXXX", sizeof(dpath)); 719 if (mkdtemp(dpath) == NULL) 720 err(-1, "mkdtemp"); 721 722 /* 723 * Set up a parent process to GC temporary storage when we're done. 724 */ 725 pid = fork(); 726 if (pid < 0) { 727 serrno = errno; 728 (void)rmdir(dpath); 729 errno = serrno; 730 err(-1, "fork"); 731 } 732 if (pid > 0) { 733 signal(SIGINT, SIG_IGN); 734 while (waitpid(pid, NULL, 0) != pid); 735 snprintf(cmd, sizeof(cmd), "%s %s", RMDIR, dpath); 736 (void)system(cmd); 737 exit(0); 738 } 739 740 printf("Start: inflight %d open %d\n", getinflight(), 741 getopenfiles()); 742 743 twosome_nothing(); 744 twosome_drop(); 745 746 threesome_nothing(); 747 threesome_drop(); 748 749 fivesome_nothing(); 750 fivesome_drop(); 751 752 complex_cycles(); 753 754 listen_nothing(); 755 listen_drop(); 756 757 listen_connect_nothing(); 758 listen_connect_drop(); 759 760 printf("Finish: inflight %d open %d\n", getinflight(), 761 getopenfiles()); 762 return (0); 763 } 764