1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 /* 29 * The copyright in this file is taken from the original Leach & Salz 30 * UUID specification, from which this implementation is derived. 31 */ 32 33 /* 34 * Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. 35 * Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & 36 * Digital Equipment Corporation, Maynard, Mass. Copyright (c) 1998 37 * Microsoft. To anyone who acknowledges that this file is provided 38 * "AS IS" without any express or implied warranty: permission to use, 39 * copy, modify, and distribute this file for any purpose is hereby 40 * granted without fee, provided that the above copyright notices and 41 * this notice appears in all source code copies, and that none of the 42 * names of Open Software Foundation, Inc., Hewlett-Packard Company, 43 * or Digital Equipment Corporation be used in advertising or 44 * publicity pertaining to distribution of the software without 45 * specific, written prior permission. Neither Open Software 46 * Foundation, Inc., Hewlett-Packard Company, Microsoft, nor Digital 47 * Equipment Corporation makes any representations about the 48 * suitability of this software for any purpose. 49 */ 50 51 /* 52 * Module: uuid.c 53 * 54 * Description: This module is the workhorse for generating abstract 55 * UUIDs. It delegates system-specific tasks (such 56 * as obtaining the node identifier or system time) 57 * to the sysdep module. 58 */ 59 60 #include <ctype.h> 61 #include <sys/param.h> 62 #include <sys/stat.h> 63 #include <errno.h> 64 #include <stdio.h> 65 #include <stdlib.h> 66 #include <strings.h> 67 #include <fcntl.h> 68 #include <unistd.h> 69 #include <uuid/uuid.h> 70 #include <thread.h> 71 #include <synch.h> 72 #include "uuid_misc.h" 73 74 #define STATE_LOCATION "/var/sadm/system/uuid_state" 75 #define URANDOM_PATH "/dev/urandom" 76 #define MAX_RETRY 8 77 #define VER1_MASK 0xefff 78 79 static mutex_t ulock = DEFAULTMUTEX; 80 81 uint16_t _get_random(void); 82 void _get_current_time(uuid_time_t *); 83 void struct_to_string(uuid_t, struct uuid *); 84 void string_to_struct(struct uuid *, uuid_t); 85 int get_ethernet_address(uuid_node_t *); 86 87 /* 88 * local functions 89 */ 90 static int _lock_state(char *); 91 static void _unlock_state(int); 92 static void _read_state(int, uint16_t *, uuid_time_t *, 93 uuid_node_t *); 94 static int _write_state(int, uint16_t, uuid_time_t, uuid_node_t); 95 static void _format_uuid(struct uuid *, uint16_t, uuid_time_t, 96 uuid_node_t); 97 static void fill_random_bytes(uchar_t *, int); 98 static int uuid_create(struct uuid *); 99 100 static void gen_ethernet_address(uuid_node_t *); 101 /* 102 * Name: uuid_create. 103 * 104 * Description: Generates a uuid based on Version 1 format 105 * 106 * Returns: 0 on success, -1 on Error 107 */ 108 static int 109 uuid_create(struct uuid *uuid) 110 { 111 uuid_time_t timestamp, last_time; 112 uint16_t clockseq = 0; 113 uuid_node_t last_node; 114 uuid_node_t system_node; 115 int locked_state_fd; 116 int non_unique = 0; 117 118 if (mutex_lock(&ulock) != 0) { 119 return (-1); 120 } 121 122 gen_ethernet_address(&system_node); 123 /* 124 * acquire system wide lock so we're alone 125 */ 126 locked_state_fd = _lock_state(STATE_LOCATION); 127 if (locked_state_fd < 0) { 128 /* couldn't create and/or lock state; don't have access */ 129 non_unique++; 130 } else { 131 /* read saved state from disk */ 132 _read_state(locked_state_fd, &clockseq, &last_time, 133 &last_node); 134 } 135 136 if (clockseq == 0) { 137 /* couldn't read clock sequence; generate a random one */ 138 clockseq = _get_random(); 139 non_unique++; 140 } 141 if (memcmp(&system_node, &last_node, sizeof (uuid_node_t)) != 0) { 142 clockseq++; 143 } 144 145 /* 146 * get current time 147 */ 148 _get_current_time(×tamp); 149 150 /* 151 * If timestamp is not set or is not in the past, 152 * increment clock sequence. 153 */ 154 if ((last_time == 0) || (last_time >= timestamp)) { 155 clockseq++; 156 last_time = timestamp; 157 } 158 159 if (non_unique) 160 system_node.nodeID[0] |= 0x80; 161 /* 162 * stuff fields into the UUID 163 */ 164 _format_uuid(uuid, clockseq, timestamp, system_node); 165 if ((locked_state_fd >= 0) && 166 (_write_state(locked_state_fd, clockseq, timestamp, 167 system_node) == -1)) { 168 _unlock_state(locked_state_fd); 169 (void) mutex_unlock(&ulock); 170 return (-1); 171 } 172 /* 173 * Unlock system-wide lock 174 */ 175 _unlock_state(locked_state_fd); 176 (void) mutex_unlock(&ulock); 177 return (0); 178 } 179 180 /* 181 * Name: gen_ethernet_address 182 * 183 * Description: Fills system_node with Ethernet address if available, 184 * else fills random numbers 185 * 186 * Returns: Nothing 187 */ 188 static void 189 gen_ethernet_address(uuid_node_t *system_node) 190 { 191 uchar_t node[6]; 192 193 if (get_ethernet_address(system_node) != 0) { 194 fill_random_bytes(node, 6); 195 (void) memcpy(system_node->nodeID, node, 6); 196 /* 197 * use 8:0:20 with the multicast bit set 198 * to avoid namespace collisions. 199 */ 200 system_node->nodeID[0] = 0x88; 201 system_node->nodeID[1] = 0x00; 202 system_node->nodeID[2] = 0x20; 203 } 204 } 205 206 /* 207 * Name: _format_uuid 208 * 209 * Description: Formats a UUID, given the clock_seq timestamp, 210 * and node address. Fills in passed-in pointer with 211 * the resulting uuid. 212 * 213 * Returns: None. 214 */ 215 static void 216 _format_uuid(struct uuid *uuid, uint16_t clock_seq, 217 uuid_time_t timestamp, uuid_node_t node) 218 { 219 220 /* 221 * First set up the first 60 bits from the timestamp 222 */ 223 uuid->time_low = (uint32_t)(timestamp & 0xFFFFFFFF); 224 uuid->time_mid = (uint16_t)((timestamp >> 32) & 0xFFFF); 225 uuid->time_hi_and_version = (uint16_t)((timestamp >> 48) & 226 0x0FFF); 227 228 /* 229 * This is version 1, so say so in the UUID version field (4 bits) 230 */ 231 uuid->time_hi_and_version |= (1 << 12); 232 233 /* 234 * Now do the clock sequence 235 */ 236 uuid->clock_seq_low = clock_seq & 0xFF; 237 238 /* 239 * We must save the most-significant 2 bits for the reserved field 240 */ 241 uuid->clock_seq_hi_and_reserved = (clock_seq & 0x3F00) >> 8; 242 243 /* 244 * The variant for this format is the 2 high bits set to 10, 245 * so here it is 246 */ 247 uuid->clock_seq_hi_and_reserved |= 0x80; 248 249 /* 250 * write result to passed-in pointer 251 */ 252 (void) memcpy(&uuid->node_addr, &node, sizeof (uuid->node_addr)); 253 } 254 255 /* 256 * Name: _read_state 257 * 258 * Description: Reads non-volatile state from a (possibly) saved statefile. 259 * For each non-null pointer passed-in, the corresponding 260 * information from the statefile is filled in. 261 * the resulting uuid. 262 * 263 * Returns: Nothing. 264 */ 265 static void 266 _read_state(int fd, uint16_t *clockseq, 267 uuid_time_t *timestamp, uuid_node_t *node) 268 { 269 uuid_state_t vol_state; 270 271 bzero(node, sizeof (uuid_node_t)); 272 *timestamp = 0; 273 *clockseq = 0; 274 275 if (read(fd, &vol_state, sizeof (vol_state)) < sizeof (vol_state)) { 276 /* This file is being accessed the first time */ 277 return; 278 } 279 280 *node = vol_state.node; 281 *timestamp = vol_state.ts; 282 *clockseq = vol_state.cs; 283 } 284 285 286 /* 287 * Name: _write_state 288 * 289 * Description: Writes non-volatile state from the passed-in information. 290 * 291 * Returns: -1 on error, 0 otherwise. 292 */ 293 static int 294 _write_state(int fd, uint16_t clockseq, 295 uuid_time_t timestamp, uuid_node_t node) 296 { 297 uuid_state_t vol_state; 298 299 vol_state.cs = clockseq; 300 vol_state.ts = timestamp; 301 vol_state.node = node; 302 /* 303 * seek to beginning of file and write data 304 */ 305 if (lseek(fd, 0, SEEK_SET) != -1) { 306 if (write(fd, &vol_state, sizeof (uuid_state_t)) != -1) { 307 return (0); 308 } 309 } 310 return (-1); 311 } 312 313 314 315 /* 316 * Name: _uuid_print 317 * 318 * Description: Prints a nicely-formatted uuid to stdout. 319 * 320 * Returns: None. 321 * 322 */ 323 void 324 uuid_print(struct uuid u) 325 { 326 int i; 327 328 (void) printf("%8.8x-%4.4x-%4.4x-%2.2x%2.2x-", u.time_low, u.time_mid, 329 u.time_hi_and_version, u.clock_seq_hi_and_reserved, 330 u.clock_seq_low); 331 for (i = 0; i < 6; i++) 332 (void) printf("%2.2x", u.node_addr[i]); 333 (void) printf("\n"); 334 } 335 336 /* 337 * Name: _lock_state 338 * 339 * Description: Locks down the statefile, by first creating the file 340 * if it doesn't exist. 341 * 342 * Returns: A non-negative file descriptor referring to the locked 343 * state file, if it was able to be created and/or locked, 344 * or -1 otherwise. 345 */ 346 static int 347 _lock_state(char *loc) 348 { 349 int fd; 350 struct flock lock; 351 352 fd = open(loc, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR); 353 354 if (fd < 0) { 355 return (-1); 356 } 357 358 lock.l_type = F_WRLCK; 359 lock.l_start = 0; 360 lock.l_whence = SEEK_SET; 361 lock.l_len = 0; 362 363 if (fcntl(fd, F_SETLKW, &lock) == -1) { 364 /* 365 * File could not be locked, bail 366 */ 367 (void) close(fd); 368 return (-1); 369 } 370 return (fd); 371 } 372 373 /* 374 * Name: _unlock_state 375 * 376 * Description: Unlocks a locked statefile, and close()'s the file. 377 * 378 * Returns: Nothing. 379 */ 380 void 381 _unlock_state(int fd) 382 { 383 struct flock lock; 384 385 lock.l_type = F_UNLCK; 386 lock.l_start = 0; 387 lock.l_whence = SEEK_SET; 388 lock.l_len = 0; 389 390 (void) fcntl(fd, F_SETLK, &lock); 391 (void) close(fd); 392 } 393 394 /* 395 * Name: fill_random_bytes 396 * 397 * Description: fills buf with random numbers - nbytes is the number of bytes 398 * to fill-in. Tries to use /dev/urandom random number generator- 399 * if that fails for some reason, it retries MAX_RETRY times. If 400 * it still fails then it uses srand48(3C) 401 * 402 * Returns: Nothing. 403 */ 404 static void 405 fill_random_bytes(uchar_t *buf, int nbytes) 406 { 407 int i, fd, retries = 0; 408 409 fd = open(URANDOM_PATH, O_RDONLY); 410 if (fd >= 0) { 411 while (nbytes > 0) { 412 i = read(fd, buf, nbytes); 413 if ((i < 0) && (errno == EINTR)) { 414 continue; 415 } 416 if (i <= 0) { 417 if (retries++ == MAX_RETRY) 418 break; 419 continue; 420 } 421 nbytes -= i; 422 buf += i; 423 retries = 0; 424 } 425 if (nbytes == 0) { 426 (void) close(fd); 427 return; 428 } 429 } 430 for (i = 0; i < nbytes; i++) { 431 *buf++ = _get_random() & 0xFF; 432 } 433 if (fd >= 0) { 434 (void) close(fd); 435 } 436 } 437 438 /* 439 * Name: struct_to_string 440 * 441 * Description: Unpacks the structure members in "struct uuid" to a char 442 * string "uuid_t". 443 * 444 * Returns: Nothing. 445 */ 446 void 447 struct_to_string(uuid_t ptr, struct uuid *uu) 448 { 449 uint_t tmp; 450 uchar_t *out = ptr; 451 452 tmp = uu->time_low; 453 out[3] = (uchar_t)tmp; 454 tmp >>= 8; 455 out[2] = (uchar_t)tmp; 456 tmp >>= 8; 457 out[1] = (uchar_t)tmp; 458 tmp >>= 8; 459 out[0] = (uchar_t)tmp; 460 461 tmp = uu->time_mid; 462 out[5] = (uchar_t)tmp; 463 tmp >>= 8; 464 out[4] = (uchar_t)tmp; 465 466 tmp = uu->time_hi_and_version; 467 out[7] = (uchar_t)tmp; 468 tmp >>= 8; 469 out[6] = (uchar_t)tmp; 470 471 tmp = uu->clock_seq_hi_and_reserved; 472 out[8] = (uchar_t)tmp; 473 tmp = uu->clock_seq_low; 474 out[9] = (uchar_t)tmp; 475 476 (void) memcpy(out+10, uu->node_addr, 6); 477 478 } 479 480 /* 481 * Name: string_to_struct 482 * 483 * Description: Packs the values in the "uuid_t" string into "struct uuid". 484 * 485 * Returns: Nothing 486 */ 487 void 488 string_to_struct(struct uuid *uuid, uuid_t in) 489 { 490 491 uchar_t *ptr; 492 uint_t tmp; 493 494 ptr = in; 495 496 tmp = *ptr++; 497 tmp = (tmp << 8) | *ptr++; 498 tmp = (tmp << 8) | *ptr++; 499 tmp = (tmp << 8) | *ptr++; 500 uuid->time_low = tmp; 501 502 tmp = *ptr++; 503 tmp = (tmp << 8) | *ptr++; 504 uuid->time_mid = tmp; 505 506 tmp = *ptr++; 507 tmp = (tmp << 8) | *ptr++; 508 uuid->time_hi_and_version = tmp; 509 510 tmp = *ptr++; 511 uuid->clock_seq_hi_and_reserved = tmp; 512 513 tmp = *ptr++; 514 uuid->clock_seq_low = tmp; 515 516 (void) memcpy(uuid->node_addr, ptr, 6); 517 518 } 519 520 /* 521 * Name: uuid_generate_random 522 * 523 * Description: Generates UUID based on DCE Version 4 524 * 525 * Returns: Nothing. uu contains the newly generated UUID 526 */ 527 void 528 uuid_generate_random(uuid_t uu) 529 { 530 531 struct uuid uuid; 532 533 if (uu == NULL) 534 return; 535 536 (void) memset(uu, 0, sizeof (uuid_t)); 537 (void) memset(&uuid, 0, sizeof (struct uuid)); 538 539 fill_random_bytes(uu, sizeof (uuid_t)); 540 string_to_struct(&uuid, uu); 541 /* 542 * This is version 4, so say so in the UUID version field (4 bits) 543 */ 544 uuid.time_hi_and_version |= (1 << 14); 545 /* 546 * we don't want the bit 1 to be set also which is for version 1 547 */ 548 uuid.time_hi_and_version &= VER1_MASK; 549 550 /* 551 * The variant for this format is the 2 high bits set to 10, 552 * so here it is 553 */ 554 uuid.clock_seq_hi_and_reserved |= 0x80; 555 556 /* 557 * Set MSB of Ethernet address to 1 to indicate that it was generated 558 * randomly 559 */ 560 uuid.node_addr[0] |= 0x80; 561 struct_to_string(uu, &uuid); 562 } 563 564 /* 565 * Name: uuid_generate_time 566 * 567 * Description: Generates UUID based on DCE Version 1. 568 * 569 * Returns: Nothing. uu contains the newly generated UUID. 570 */ 571 void 572 uuid_generate_time(uuid_t uu) 573 { 574 struct uuid uuid; 575 576 if (uu == NULL) 577 return; 578 579 if (uuid_create(&uuid) == -1) { 580 uuid_generate_random(uu); 581 return; 582 } 583 struct_to_string(uu, &uuid); 584 } 585 586 /* 587 * Name: uuid_generate 588 * 589 * Description: Creates a new UUID. The uuid will be generated based on 590 * high-quality randomness from /dev/urandom, if available by 591 * calling uuid_generate_random. If it failed to generate UUID 592 * then uuid_generate will call uuid_generate_time. 593 * 594 * Returns: Nothing. uu contains the newly generated UUID. 595 */ 596 void 597 uuid_generate(uuid_t uu) 598 { 599 int fd; 600 601 if (uu == NULL) { 602 return; 603 } 604 fd = open(URANDOM_PATH, O_RDONLY); 605 if (fd >= 0) { 606 (void) close(fd); 607 uuid_generate_random(uu); 608 } else { 609 (void) uuid_generate_time(uu); 610 } 611 } 612 613 /* 614 * Name: uuid_copy 615 * 616 * Description: The uuid_copy function copies the UUID variable src to dst 617 * 618 * Returns: Nothing 619 */ 620 void 621 uuid_copy(uuid_t dst, uuid_t src) 622 { 623 624 (void) memcpy(dst, src, UUID_LEN); 625 } 626 627 /* 628 * Name: uuid_clear 629 * 630 * Description: The uuid_clear function sets the value of the supplied uuid 631 * variable uu, to the NULL value. 632 * 633 * Returns: Nothing 634 */ 635 void 636 uuid_clear(uuid_t uu) 637 { 638 (void) memset(uu, 0, UUID_LEN); 639 } 640 641 /* 642 * Name: uuid_unparse 643 * 644 * Description: This function converts the supplied UUID uu from the internal 645 * binary format into a 36-byte string (plus trailing null char) 646 * and stores this value in the character string pointed to by out 647 * 648 * Returns: Nothing. 649 */ 650 void 651 uuid_unparse(uuid_t uu, char *out) 652 { 653 struct uuid uuid; 654 uint16_t clock_seq; 655 char etheraddr[13]; 656 int index = 0, i; 657 658 /* basic sanity checking */ 659 if (uu == NULL) { 660 return; 661 } 662 663 /* XXX user should have allocated enough memory */ 664 /* 665 * if (strlen(out) < UUID_PRINTABLE_STRING_LENGTH) { 666 * return; 667 * } 668 */ 669 string_to_struct(&uuid, uu); 670 clock_seq = uuid.clock_seq_hi_and_reserved; 671 clock_seq = (clock_seq << 8) | uuid.clock_seq_low; 672 for (i = 0; i < 6; i++) { 673 (void) sprintf(ðeraddr[index++], "%.2x", uuid.node_addr[i]); 674 index++; 675 } 676 etheraddr[index] = '\0'; 677 678 (void) snprintf(out, 25, "%08x-%04x-%04x-%04x-", 679 uuid.time_low, uuid.time_mid, uuid.time_hi_and_version, 680 clock_seq); 681 (void) strlcat(out, etheraddr, UUID_PRINTABLE_STRING_LENGTH); 682 } 683 684 /* 685 * Name: uuid_is_null 686 * 687 * Description: The uuid_is_null function compares the value of the supplied 688 * UUID variable uu to the NULL value. If the value is equal 689 * to the NULL UUID, 1 is returned, otherwise 0 is returned. 690 * 691 * Returns: 0 if uu is NOT null, 1 if uu is NULL. 692 */ 693 int 694 uuid_is_null(uuid_t uu) 695 { 696 int i; 697 uuid_t null_uu; 698 699 (void) memset(null_uu, 0, sizeof (uuid_t)); 700 i = memcmp(uu, null_uu, sizeof (uuid_t)); 701 if (i == 0) { 702 /* uu is NULL uuid */ 703 return (1); 704 } else { 705 return (0); 706 } 707 } 708 709 /* 710 * Name: uuid_parse 711 * 712 * Description: uuid_parse converts the UUID string given by 'in' into the 713 * internal uuid_t format. The input UUID is a string of the form 714 * cefa7a9c-1dd2-11b2-8350-880020adbeef in printf(3C) format. 715 * Upon successfully parsing the input string, UUID is stored 716 * in the location pointed to by uu 717 * 718 * Returns: 0 if the UUID is successfully stored, -1 otherwise. 719 */ 720 int 721 uuid_parse(char *in, uuid_t uu) 722 { 723 724 char *ptr, buf[3]; 725 int i; 726 struct uuid uuid; 727 uint16_t clock_seq; 728 729 /* do some sanity checking */ 730 if ((strlen(in) != 36) || (uu == NULL) || (in[36] != '\0')) { 731 return (-1); 732 } 733 734 ptr = in; 735 for (i = 0; i < 36; i++, ptr++) { 736 if ((i == 8) || (i == 13) || (i == 18) || (i == 23)) { 737 if (*ptr != '-') { 738 return (-1); 739 } 740 } else { 741 if (!isxdigit(*ptr)) { 742 return (-1); 743 } 744 } 745 } 746 747 uuid.time_low = strtoul(in, NULL, 16); 748 uuid.time_mid = strtoul(in+9, NULL, 16); 749 uuid.time_hi_and_version = strtoul(in+14, NULL, 16); 750 clock_seq = strtoul(in+19, NULL, 16); 751 uuid.clock_seq_hi_and_reserved = (clock_seq & 0xFF00) >> 8; 752 uuid.clock_seq_low = (clock_seq & 0xFF); 753 754 ptr = in+24; 755 buf[2] = '\0'; 756 for (i = 0; i < 6; i++) { 757 buf[0] = *ptr++; 758 buf[1] = *ptr++; 759 uuid.node_addr[i] = strtoul(buf, NULL, 16); 760 } 761 struct_to_string(uu, &uuid); 762 return (0); 763 } 764 765 /* 766 * Name: uuid_time 767 * 768 * Description: uuid_time extracts the time at which the supplied UUID uu 769 * was created. This function can only extract the creation 770 * time for UUIDs created with the uuid_generate_time function. 771 * The time at which the UUID was created, in seconds and 772 * microseconds since the epoch is stored in the location 773 * pointed to by ret_tv. 774 * 775 * Returns: The time at which the UUID was created, in seconds since 776 * January 1, 1970 GMT (the epoch). -1 otherwise. 777 */ 778 time_t 779 uuid_time(uuid_t uu, struct timeval *ret_tv) 780 { 781 struct uuid uuid; 782 uint_t high; 783 struct timeval tv; 784 u_longlong_t clock_reg; 785 uint_t tmp; 786 uint8_t clk; 787 788 string_to_struct(&uuid, uu); 789 tmp = (uuid.time_hi_and_version & 0xF000) >> 12; 790 clk = uuid.clock_seq_hi_and_reserved; 791 792 /* check if uu is NULL, Version = 1 of DCE and Variant = 0b10x */ 793 if ((uu == NULL) || ((tmp & 0x01) != 0x01) || ((clk & 0x80) != 0x80)) { 794 return (-1); 795 } 796 high = uuid.time_mid | ((uuid.time_hi_and_version & 0xFFF) << 16); 797 clock_reg = uuid.time_low | ((u_longlong_t)high << 32); 798 799 clock_reg -= (((u_longlong_t)0x01B21DD2) << 32) + 0x13814000; 800 tv.tv_sec = clock_reg / 10000000; 801 tv.tv_usec = (clock_reg % 10000000) / 10; 802 803 if (ret_tv) { 804 *ret_tv = tv; 805 } 806 807 return (tv.tv_sec); 808 } 809