Changeset 706:4ffbc9f1e922 in freeDiameter for libfdproto/dictionary.c
- Timestamp:
- Feb 9, 2011, 3:26:58 PM (13 years ago)
- Branch:
- default
- Phase:
- public
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
libfdproto/dictionary.c
r687 r706 63 63 64 64 union { 65 struct dict_vendor_data vendor; 66 struct dict_application_data application; 67 struct dict_type_data type; 68 struct dict_enumval_data enumval; 69 struct dict_avp_data avp; 70 struct dict_cmd_data cmd; 71 struct dict_rule_data rule; 65 struct dict_vendor_data vendor; /* datastr_len = strlen(vendor_name) */ 66 struct dict_application_data application; /* datastr_len = strlen(application_name) */ 67 struct dict_type_data type; /* datastr_len = strlen(type_name) */ 68 struct dict_enumval_data enumval; /* datastr_len = strlen(enum_name) */ 69 struct dict_avp_data avp; /* datastr_len = strlen(avp_name) */ 70 struct dict_cmd_data cmd; /* datastr_len = strlen(cmd_name) */ 71 struct dict_rule_data rule; /* datastr_len = 0 */ 72 72 } data; /* The data of this object */ 73 74 size_t datastr_len; /* cached length of the string inside the data. Saved when the object is created. */ 73 75 74 76 struct dict_object * parent; /* The parent of this object, if any */ … … 88 90 list[0]: list of the vendors, ordered by their id. The sentinel is g_dict_vendors (vendor with id 0) 89 91 list[1]: sentinel for the list of AVPs from this vendor, ordered by AVP code. 90 list[2]: sentinel for the list of AVPs from this vendor, ordered by AVP name .92 list[2]: sentinel for the list of AVPs from this vendor, ordered by AVP name (fd_os_cmp). 91 93 92 94 => APPLICATIONS: … … 97 99 => TYPES: 98 100 list[0]: list of the types, ordered by their names. The sentinel is g_list_types. 99 list[1]: sentinel for the type_enum list of this type, ordered by their constant name .101 list[1]: sentinel for the type_enum list of this type, ordered by their constant name (fd_os_cmp). 100 102 list[2]: sentinel for the type_enum list of this type, ordered by their constant value. 101 103 102 104 => TYPE_ENUMS: 103 list[0]: list of the contants for a given type, ordered by the constant name . Sentinel is a (list[1]) element of a TYPE object.105 list[0]: list of the contants for a given type, ordered by the constant name (fd_os_cmp). Sentinel is a (list[1]) element of a TYPE object. 104 106 list[1]: list of the contants for a given type, ordered by the constant value. Sentinel is a (list[2]) element of a TYPE object. 105 107 list[2]: not used … … 107 109 => AVPS: 108 110 list[0]: list of the AVP from a given vendor, ordered by avp code. Sentinel is a list[1] element of a VENDOR object. 109 list[1]: list of the AVP from a given vendor, ordered by avp name . Sentinel is a list[2] element of a VENDOR object.111 list[1]: list of the AVP from a given vendor, ordered by avp name (fd_os_cmp). Sentinel is a list[2] element of a VENDOR object. 110 112 list[2]: sentinel for the rule list that apply to this AVP. 111 113 112 114 => COMMANDS: 113 list[0]: list of the commands, ordered by their names . The sentinel is g_list_cmd_name.115 list[0]: list of the commands, ordered by their names (fd_os_cmp). The sentinel is g_list_cmd_name. 114 116 list[1]: list of the commands, ordered by their command code and 'R' flag. The sentinel is g_list_cmd_code. 115 117 list[2]: sentinel for the rule list that apply to this command. 116 118 117 119 => RULES: 118 list[0]: list of the rules for a given (grouped) AVP or Command, ordered by the AVP name to which they refer. sentinel is list[2] of a command or (grouped) avp.120 list[0]: list of the rules for a given (grouped) AVP or Command, ordered by the AVP vendor & code to which they refer. sentinel is list[2] of a command or (grouped) avp. 119 121 list[1]: not used 120 122 list[2]: not used. … … 223 225 /* Functions to manage the objects creation and destruction. */ 224 226 225 /* Duplicate a string inplace */226 #define DUP_string ( str ) {\227 char * __str = (str);\228 CHECK_MALLOC( (str) = strdup(__str)); \227 /* Duplicate a string inplace, save its length */ 228 #define DUP_string_len( str, plen ) { \ 229 *(plen) = strlen((str)); \ 230 str = os0dup( str, *(plen)); \ 229 231 } 230 232 … … 259 261 260 262 /* Initialize the "data" part of an object */ 261 static int init_object_data( void* dest, void * source, enum dict_object_type type)263 static int init_object_data(struct dict_object * dest, void * source, enum dict_object_type type) 262 264 { 263 265 TRACE_ENTRY("%p %p %d", dest, source, type); … … 265 267 266 268 /* Generic: copy the full data structure */ 267 memcpy( dest, source, dict_obj_info[type].datasize );269 memcpy( &dest->data, source, dict_obj_info[type].datasize ); 268 270 269 271 /* Then strings must be duplicated, not copied */ … … 271 273 switch (type) { 272 274 case DICT_VENDOR: 273 DUP_string ( ((struct dict_vendor_data *)dest)->vendor_name);275 DUP_string_len( dest->data.vendor.vendor_name, &dest->datastr_len ); 274 276 break; 275 277 276 278 case DICT_APPLICATION: 277 DUP_string ( ((struct dict_application_data *)dest)->application_name);279 DUP_string_len( dest->data.application.application_name, &dest->datastr_len ); 278 280 break; 279 281 280 282 case DICT_TYPE: 281 DUP_string ( ((struct dict_type_data *)dest)->type_name);283 DUP_string_len( dest->data.type.type_name, &dest->datastr_len ); 282 284 break; 283 285 284 286 case DICT_ENUMVAL: 285 DUP_string ( ((struct dict_enumval_data *)dest)->enum_name);287 DUP_string_len( dest->data.enumval.enum_name, &dest->datastr_len ); 286 288 break; 287 289 288 290 case DICT_AVP: 289 DUP_string ( ((struct dict_avp_data *)dest)->avp_name);291 DUP_string_len( dest->data.avp.avp_name, &dest->datastr_len ); 290 292 break; 291 293 292 294 case DICT_COMMAND: 293 DUP_string ( ((struct dict_cmd_data *)dest)->cmd_name);295 DUP_string_len( dest->data.cmd.cmd_name, &dest->datastr_len ); 294 296 break; 295 297 … … 456 458 TRACE_ENTRY("%p %p", o1, o2); 457 459 458 return strcmp( o1->data.type.type_name, o2->data.type.type_name);460 return fd_os_cmp( o1->data.type.type_name, o1->datastr_len, o2->data.type.type_name, o2->datastr_len ); 459 461 } 460 462 … … 464 466 TRACE_ENTRY("%p %p", o1, o2); 465 467 466 return strcmp( o1->data.enumval.enum_name, o2->data.enumval.enum_name);468 return fd_os_cmp( o1->data.enumval.enum_name, o1->datastr_len, o2->data.enumval.enum_name, o2->datastr_len ); 467 469 } 468 470 … … 470 472 static int order_enum_by_val ( struct dict_object *o1, struct dict_object *o2 ) 471 473 { 472 size_t oslen;473 int cmp = 0;474 475 474 TRACE_ENTRY("%p %p", o1, o2); 476 475 … … 478 477 switch ( o1->parent->data.type.type_base ) { 479 478 case AVP_TYPE_OCTETSTRING: 480 oslen = o1->data.enumval.enum_value.os.len; 481 if (o2->data.enumval.enum_value.os.len < oslen) 482 oslen = o2->data.enumval.enum_value.os.len; 483 cmp = memcmp(o1->data.enumval.enum_value.os.data, o2->data.enumval.enum_value.os.data, oslen ); 484 return (cmp ? cmp : ORDER_scalar(o1->data.enumval.enum_value.os.len,o2->data.enumval.enum_value.os.len)); 479 return fd_os_cmp( o1->data.enumval.enum_value.os.data, o1->data.enumval.enum_value.os.len, 480 o2->data.enumval.enum_value.os.data, o2->data.enumval.enum_value.os.len); 485 481 486 482 case AVP_TYPE_INTEGER32: … … 522 518 TRACE_ENTRY("%p %p", o1, o2); 523 519 524 return strcmp( o1->data.avp.avp_name, o2->data.avp.avp_name);520 return fd_os_cmp( o1->data.avp.avp_name, o1->datastr_len, o2->data.avp.avp_name, o2->datastr_len ); 525 521 } 526 522 … … 530 526 TRACE_ENTRY("%p %p", o1, o2); 531 527 532 return strcmp( o1->data.cmd.cmd_name, o2->data.cmd.cmd_name);528 return fd_os_cmp( o1->data.cmd.cmd_name, o1->datastr_len, o2->data.cmd.cmd_name, o2->datastr_len ); 533 529 } 534 530 … … 555 551 556 552 /* Compare two rule object by the AVP vendor & code that they refer (checks already performed) */ 557 static int order_rule_by_avp n( struct dict_object *o1, struct dict_object *o2 )553 static int order_rule_by_avpvc ( struct dict_object *o1, struct dict_object *o2 ) 558 554 { 559 555 TRACE_ENTRY("%p %p", o1, o2); … … 590 586 591 587 /* For search of strings in lists. isindex= 1 if the string is the ordering key of the list */ 592 #define SEARCH_string( str, sentinel, datafield, isindex ) { \ 593 char * __str = (char *) str; \ 588 /* it is expected that object->datastr_len is the length of the datafield parameter */ 589 #define SEARCH_os0_l( str, len, sentinel, datafield, isindex ) { \ 590 char * __str = (char *) (str); \ 591 size_t __strlen = (size_t)(len); \ 594 592 int __cmp; \ 595 593 struct fd_list * __li; \ 596 594 ret = 0; \ 597 595 for (__li = (sentinel)->next; __li != (sentinel); __li = __li->next) { \ 598 __cmp = strcmp(__str, _O(__li->o)->data. datafield ); \ 596 __cmp = fd_os_cmp(__str, __strlen, \ 597 _O(__li->o)->data. datafield, _O(__li->o)->datastr_len);\ 599 598 if (__cmp == 0) { \ 600 599 if (result) \ … … 611 610 } 612 611 613 /* For search of octetstrings in lists (not \0 terminated). */ 614 #define SEARCH_ocstring( ostr, length, sentinel, osdatafield, isindex ) { \ 615 unsigned char * __ostr = (unsigned char *) ostr; \ 612 /* When len is not provided */ 613 #define SEARCH_os0( str, sentinel, datafield, isindex ) { \ 614 char * _str = (char *) (str); \ 615 size_t _strlen = strlen(_str); \ 616 SEARCH_os0_l( _str, _strlen, sentinel, datafield, isindex ); \ 617 } 618 619 620 /* For search of octetstrings in lists. */ 621 #define SEARCH_os( str, strlen, sentinel, osdatafield, isindex ) { \ 622 uint8_t * __str = (uint8_t *) (str); \ 623 size_t __strlen = (size_t)(strlen); \ 616 624 int __cmp; \ 617 size_t __len; \618 625 struct fd_list * __li; \ 619 626 ret = 0; \ 620 for (__li = (sentinel); __li->next != (sentinel); __li = __li->next) { \ 621 __len = _O(__li->next->o)->data. osdatafield .len; \ 622 if ( __len > (length) ) \ 623 __len = (length); \ 624 __cmp = memcmp(__ostr, \ 625 _O(__li->next->o)->data. osdatafield .data, \ 626 __len); \ 627 if (! __cmp) { \ 628 __cmp = ORDER_scalar( length, \ 629 _O(__li->next->o)->data. osdatafield .len); \ 630 } \ 627 for (__li = (sentinel)->next; __li != (sentinel); __li = __li->next) { \ 628 __cmp = fd_os_cmp(__str, __strlen, \ 629 _O(__li->o)->data. osdatafield .data, \ 630 _O(__li->o)->data. osdatafield .len); \ 631 631 if (__cmp == 0) { \ 632 632 if (result) \ 633 *result = _O(__li-> next->o);\633 *result = _O(__li->o); \ 634 634 goto end; \ 635 635 } \ … … 644 644 645 645 /* For search of AVP name in rule lists. */ 646 #define SEARCH_ruleavpname( str, sentinel ) { \ 647 char * __str = (char *) str; \ 646 #define SEARCH_ruleavpname( str, strlen, sentinel ) { \ 647 char * __str = (char *) (str); \ 648 size_t __strlen = (size_t) (strlen); \ 648 649 int __cmp; \ 649 650 struct fd_list * __li; \ 650 651 ret = 0; \ 651 for (__li = (sentinel); __li->next != (sentinel); __li = __li->next) { \ 652 __cmp = strcmp(__str, \ 653 _O(__li->next->o)->data.rule.rule_avp->data.avp.avp_name);\ 652 for (__li = (sentinel)->next; __li != (sentinel); __li = __li->next) { \ 653 __cmp = fd_os_cmp(__str, __strlen, \ 654 _O(__li->o)->data.rule.rule_avp->data.avp.avp_name, \ 655 _O(__li->o)->data.rule.rule_avp->datastr_len); \ 654 656 if (__cmp == 0) { \ 655 657 if (result) \ 656 *result = _O(__li-> next->o);\658 *result = _O(__li->o); \ 657 659 goto end; \ 658 660 } \ … … 677 679 goto end; \ 678 680 } \ 679 for (__li = (sentinel) ; __li->next!= (sentinel); __li = __li->next) { \680 __cmp= ORDER_scalar(value, _O(__li-> next->o)->data. datafield );\681 for (__li = (sentinel)->next; __li != (sentinel); __li = __li->next) { \ 682 __cmp= ORDER_scalar(value, _O(__li->o)->data. datafield ); \ 681 683 if (__cmp == 0) { \ 682 684 if (result) \ 683 *result = _O(__li-> next->o);\685 *result = _O(__li->o); \ 684 686 goto end; \ 685 687 } \ … … 698 700 struct fd_list * __li; \ 699 701 ret = 0; \ 700 for ( __li = (sentinel); \ 701 __li->next != (sentinel); \ 702 __li = __li->next) { \ 702 for (__li = (sentinel)->next; __li != (sentinel); __li = __li->next) { \ 703 703 __cmp = ORDER_scalar(value, \ 704 _O(__li-> next->o)->data.cmd.cmd_code ); \704 _O(__li->o)->data.cmd.cmd_code ); \ 705 705 if (__cmp == 0) { \ 706 706 uint8_t __mask, __val; \ 707 __mask = _O(__li-> next->o)->data.cmd.cmd_flag_mask;\708 __val = _O(__li-> next->o)->data.cmd.cmd_flag_val;\707 __mask = _O(__li->o)->data.cmd.cmd_flag_mask; \ 708 __val = _O(__li->o)->data.cmd.cmd_flag_val; \ 709 709 if ( ! (__mask & CMD_FLAG_REQUEST) ) \ 710 710 continue; \ … … 712 712 continue; \ 713 713 if (result) \ 714 *result = _O(__li-> next->o);\714 *result = _O(__li->o); \ 715 715 goto end; \ 716 716 } \ … … 739 739 case VENDOR_BY_NAME: 740 740 /* "what" is a vendor name */ 741 SEARCH_ string( what, &dict->dict_vendors.list[0], vendor.vendor_name, 0);741 SEARCH_os0( what, &dict->dict_vendors.list[0], vendor.vendor_name, 0); 742 742 break; 743 743 … … 771 771 case APPLICATION_BY_NAME: 772 772 /* "what" is an application name */ 773 SEARCH_ string( what, &dict->dict_applications.list[0], application.application_name, 0);773 SEARCH_os0( what, &dict->dict_applications.list[0], application.application_name, 0); 774 774 break; 775 775 … … 801 801 case TYPE_BY_NAME: 802 802 /* "what" is a type name */ 803 SEARCH_ string( what, &dict->dict_types, type.type_name, 1);803 SEARCH_os0( what, &dict->dict_types, type.type_name, 1); 804 804 break; 805 805 … … 850 850 if ( _what->search.enum_name != NULL ) { 851 851 /* We are looking for this string */ 852 SEARCH_ string( _what->search.enum_name, &parent->list[1], enumval.enum_name, 1 );852 SEARCH_os0( _what->search.enum_name, &parent->list[1], enumval.enum_name, 1 ); 853 853 } else { 854 854 /* We are looking for the value in enum_value */ 855 855 switch (parent->data.type.type_base) { 856 856 case AVP_TYPE_OCTETSTRING: 857 SEARCH_o cstring(_what->search.enum_value.os.data,857 SEARCH_os( _what->search.enum_value.os.data, 858 858 _what->search.enum_value.os.len, 859 859 &parent->list[2], … … 946 946 case AVP_BY_NAME: 947 947 /* "what" is the AVP name, vendor 0 */ 948 SEARCH_ string( what, &dict->dict_vendors.list[2], avp.avp_name, 1);948 SEARCH_os0( what, &dict->dict_vendors.list[2], avp.avp_name, 1); 949 949 break; 950 950 … … 969 969 /* We now have our vendor = head of the appropriate avp list */ 970 970 if (criteria == AVP_BY_NAME_AND_VENDOR) { 971 SEARCH_ string( _what->avp_name, &vendor->list[2], avp.avp_name, 1);971 SEARCH_os0( _what->avp_name, &vendor->list[2], avp.avp_name, 1); 972 972 } else { 973 973 /* AVP_BY_CODE_AND_VENDOR */ 974 SEARCH_scalar( _what->avp_code, &vendor->list[1], 974 SEARCH_scalar( _what->avp_code, &vendor->list[1], avp.avp_code, 1, (struct dict_object *)NULL ); 975 975 } 976 976 } … … 980 980 { 981 981 struct fd_list * li; 982 size_t wl = strlen((char *)what); 982 983 983 984 /* First, search for vendor 0 */ 984 SEARCH_ string( what, &dict->dict_vendors.list[2], avp.avp_name, 1);985 SEARCH_os0_l( what, wl, &dict->dict_vendors.list[2], avp.avp_name, 1); 985 986 986 987 /* If not found, loop for all vendors, until found */ 987 988 for (li = dict->dict_vendors.list[0].next; li != &dict->dict_vendors.list[0]; li = li->next) { 988 SEARCH_ string( what, &_O(li->o)->list[2], avp.avp_name, 1);989 SEARCH_os0_l( what, wl, &_O(li->o)->list[2], avp.avp_name, 1); 989 990 } 990 991 } … … 1008 1009 case CMD_BY_NAME: 1009 1010 /* "what" is a command name */ 1010 SEARCH_ string( what, &dict->dict_cmd_name, cmd.cmd_name, 1);1011 SEARCH_os0( what, &dict->dict_cmd_name, cmd.cmd_name, 1); 1011 1012 break; 1012 1013 … … 1096 1097 1097 1098 /* Perform the search */ 1098 SEARCH_ruleavpname( avp->data.avp.avp_name, &parent->list[2]);1099 SEARCH_ruleavpname( avp->data.avp.avp_name, avp->datastr_len, &parent->list[2]); 1099 1100 1100 1101 } … … 1274 1275 dump_object( &dict->dict_vendors, 0, 3, 0 ); 1275 1276 1276 fd_log_debug("###### Dumping applications#######\n");1277 fd_log_debug("###### Dumping applications #######\n"); 1277 1278 1278 1279 dump_object( &dict->dict_applications, 0, 1, 0 ); 1279 1280 1280 fd_log_debug("###### Dumping types#######\n");1281 fd_log_debug("###### Dumping types #######\n"); 1281 1282 1282 1283 dump_list( &dict->dict_types, 0, 2, 0 ); 1283 1284 1284 fd_log_debug("###### Dumping commands per name#######\n");1285 fd_log_debug("###### Dumping commands per name #######\n"); 1285 1286 1286 1287 dump_list( &dict->dict_cmd_name, 0, 2, 0 ); 1287 1288 1288 fd_log_debug("###### Dumping commands per code and flags#######\n");1289 fd_log_debug("###### Dumping commands per code and flags #######\n"); 1289 1290 1290 1291 dump_list( &dict->dict_cmd_code, 0, 0, 0 ); 1291 1292 1292 fd_log_debug("###### Statistics#######\n");1293 fd_log_debug("###### Statistics #######\n"); 1293 1294 1294 1295 for (i=1; i<=DICT_TYPE_MAX; i++) … … 1561 1562 /* Initialize the data of the new object */ 1562 1563 init_object(new, type); 1563 init_object_data( &new->data, data, type);1564 init_object_data(new, data, type); 1564 1565 new->dico = dict; 1565 1566 new->parent = parent; … … 1632 1633 case DICT_RULE: 1633 1634 /* A rule object is linked in list[2] of its parent command or AVP by the name of the AVP it refers */ 1634 ret = fd_list_insert_ordered ( &parent->list[2], &new->list[0], (int (*)(void*, void *))order_rule_by_avp n, (void **)&locref );1635 ret = fd_list_insert_ordered ( &parent->list[2], &new->list[0], (int (*)(void*, void *))order_rule_by_avpvc, (void **)&locref ); 1635 1636 if (ret) 1636 1637 goto error_unlock; … … 1659 1660 switch (type) { 1660 1661 case DICT_VENDOR: 1661 /* if we are here, it meas the two vendor id are identical */ 1662 if (strcmp(locref->data.vendor.vendor_name, new->data.vendor.vendor_name)) { 1662 /* if we are here, it means the two vendors id are identical */ 1663 if (fd_os_cmp(locref->data.vendor.vendor_name, locref->datastr_len, 1664 new->data.vendor.vendor_name, new->datastr_len)) { 1663 1665 TRACE_DEBUG(FULL, "Conflicting vendor name"); 1664 1666 break; … … 1670 1672 case DICT_APPLICATION: 1671 1673 /* got same id */ 1672 if (strcmp(locref->data.application.application_name, new->data.application.application_name)) { 1674 if (fd_os_cmp(locref->data.application.application_name, locref->datastr_len, 1675 new->data.application.application_name, new->datastr_len)) { 1673 1676 TRACE_DEBUG(FULL, "Conflicting application name"); 1674 1677 break; … … 1763 1766 1764 1767 case DICT_RULE: 1765 /* Both rules point to the same AVPs */1768 /* Both rules point to the same AVPs (code & vendor) */ 1766 1769 if (locref->data.rule.rule_position != new->data.rule.rule_position) { 1767 1770 TRACE_DEBUG(FULL, "Conflicting rule position"); … … 1847 1850 struct dictionary * new = NULL; 1848 1851 1849 TRACE_ENTRY(" ");1852 TRACE_ENTRY("%p", dict); 1850 1853 1851 1854 /* Sanity checks */ … … 1865 1868 /* Initialize the sentinel for vendors and AVP lists */ 1866 1869 init_object( &new->dict_vendors, DICT_VENDOR ); 1867 new->dict_vendors.data.vendor.vendor_name = "(no vendor)"; 1870 #define NO_VENDOR_NAME "(no vendor)" 1871 new->dict_vendors.data.vendor.vendor_name = NO_VENDOR_NAME; 1872 new->dict_vendors.datastr_len = CONSTSTRLEN(NO_VENDOR_NAME); 1868 1873 new->dict_vendors.list[0].o = NULL; /* overwrite since element is also sentinel for this list. */ 1869 1874 new->dict_vendors.dico = new; … … 1871 1876 /* Initialize the sentinel for applications */ 1872 1877 init_object( &new->dict_applications, DICT_APPLICATION ); 1873 new->dict_applications.data.application.application_name = "Diameter Common Messages"; 1878 #define APPLICATION_0_NAME "Diameter Common Messages" 1879 new->dict_applications.data.application.application_name = APPLICATION_0_NAME; 1880 new->dict_applications.datastr_len = CONSTSTRLEN(APPLICATION_0_NAME); 1874 1881 new->dict_applications.list[0].o = NULL; /* overwrite since since element is also sentinel for this list. */ 1875 1882 new->dict_applications.dico = new; … … 1884 1891 /* Initialize the error command object */ 1885 1892 init_object( &new->dict_cmd_error, DICT_COMMAND ); 1886 new->dict_cmd_error.data.cmd.cmd_name="(generic error format)"; 1893 #define GENERIC_ERROR_NAME "(generic error format)" 1894 new->dict_cmd_error.data.cmd.cmd_name = GENERIC_ERROR_NAME; 1895 new->dict_cmd_error.datastr_len = CONSTSTRLEN(GENERIC_ERROR_NAME); 1887 1896 new->dict_cmd_error.data.cmd.cmd_flag_mask=CMD_FLAG_ERROR | CMD_FLAG_REQUEST | CMD_FLAG_RETRANSMIT; 1888 1897 new->dict_cmd_error.data.cmd.cmd_flag_val =CMD_FLAG_ERROR;
Note: See TracChangeset
for help on using the changeset viewer.