1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378
|
% file: .../doc/c-lib.tex
% $Header: /usr/app/odstb/CVS/snacc/doc/c-lib.tex,v 1.1 1997/01/01 22:47:34 rj Exp $
% $Log: c-lib.tex,v $
% Revision 1.1 1997/01/01 22:47:34 rj
% first check-in
%
\chapter{\label{lib-C-chapter}C ASN.1 Library}
\section{\label{libover-C-section}Overview}
Each library type has a file in the {\ufn \dots/c-lib/src/} and
{\ufn \dots/c-lib/inc/} directories. Each source file contains the encode,
decode, free and print routines for the given type. This chapter
contains a description of each library type and its routines.
This library is also referred to as the runtime library.
After installing Snacc, you should test the library types to make sure
that they are encoding and decoding properly. Use the
{\ufn \dots/c-examples/test-lib/} example to check them.
In addition to other errors, most decoding routines will report an
error if they attempt to read past the end of the data. Be aware that
some buffer types do not support this type of checking. This is
explained more in the buffer management section.
\section{\label{tag-C-section}Tags}
Snacc's tag representation was motivated by several things.
\begin{enumerate}
\item the tags must be easy to compare for equality in {\C if} and {\C switch} statements to make tag-based decisions cheap.
\item a tag must be cheap to decode.
\item a tag must be cheap to encode.
\end{enumerate}
The first requirement meant that tags had to be integer types (for the
{\C switch} statement). The representation of the tag within the integer
was set by the second requirement.
The best way to decode cheaply is minimize the transformation between
the encoded and decoded (internal) format. So the four (can be set-up
for two) bytes of the long integer are used to hold the encoded tag,
starting with the first octet of the tag in the most significant byte
of the integer and the rest (if any) following. Any unused (always
trailing) bytes in the integer are zero. This limits the
representable tag code to less than $2^{21}$ but for reasonable ASN.1
specifications this should not be a problem.
To meet the third requirement the decoded tag representation was
bypassed entirely by using macros ({\C BEncTag1()} etc.) that
write the encoded tag octet(s) to the buffer. The writing of an
encoded tag octet involves bit shifting, bitwise ands and bitwise ors
with constant values; most optimizing C compilers can compute these at
compile time. This simplifies encoding a tag to writing some constant
byte value(s) to the buffer.
The following excerpt from {\ufn \dots/c-lib/inc/asn-tag.h} shows some
of the tag routines.
\begin{small}
\begin{verbatim}
typedef unsigned long int AsnTag;
#define MAKE_TAG_ID( class, form, code) ...
#define TAG_IS_CONS( tag) ...
#define BEncTag1( b, class, form, code) ...
#define BEncTag2( b, class, form, code) ...
#define BEncTag3( b, class, form, code) ...
#define BEncTag4( b, class, form, code) ...
#define BEncTag5( b, class, form, code) ...
AsnTag BDecTag (BUF_TYPE b, AsnLen *bytesDecoded, ENV_TYPE env);
\end{verbatim}
\end{small}
The generated decode routines use the {\C BDecTag} to decode a tag
from the buffer. The returned tag value is either used in an
{\C if} expression or as the argument to {\C switch} statements.
The {\C MAKE\_TAG\_ID} macro is used to make a tag for comparison to
the one returned by {\C BDecTag}. The {\C MAKE\_TAG\_ID} is used is
{\C switch} statement case labels and in {\C if} statements.
Most of the time tags are only compared for equality, however, the
OCTET STRING and BIT STRING decoders check the constructed bit in the
tag using the {\C TAG\_IS\_CONS} macro.
The {\C BEncTag} macros are quite fragile because they return the
encoded length of the tag; they cannot be treated as a single
statement. This requires careful use of braces when using them in
your own code in places such as the sole statement in an {\C if}
statement. This ugliness is caused by the difficulty in returning
values from multi-line macros (macros are used for performance here
since encoding tags can be a significant part of BER encoding).
The {\C BDecTag} routine will report an error via {\C longjmp} if
the encoded tag is longer than can be held in the {\C AsnTag} type
or if it read past the end of the data when decoding the tag.
\section{\label{len-C-section}Lengths}
Decoded lengths are represented by unsigned long integers, with the
maximum value indicating indefinite length.
Snacc users can choose between using only indefinite or only definite
lengths when encoding constructed values' lengths when compiling the
generated code. Of course, the generated decoders can handle both
forms. Define the {\C USE\_INDEF\_LEN} symbol when compiling the
generated code if you want to use indefinite lengths when encoding
constructed values. Primitive values are always encoded with definite
lengths as required by the standard; this is necessary to avoid
confusion between a value's content and the End-Of-Contents marker.
There is no loss of performance when using definite lengths with snacc
encoders. This is due the ``backwards'' encoding as described in
Section~\ref{encode-gen-C-section}. The schemes used by other compilers'
encoders to handle definite lengths may hurt performance.
Most of the routines in the following code are obvious except for
{\C BEncDefLenTo127()}. This is used instead of {\C BEncDefLen}
in the generated code when the compiler knows the value being encoded
will not be over 127 octets long. Values such as BOOLEANs,
INTEGERs, and REALs are assumed to be shorter than 127 octets
(constraints on the decoded representation of INTEGERs and REALs make
this valid).
\begin{small}
\begin{verbatim}
typedef unsigned long int AsnLen;
/* max unsigned value - used for internal rep of indef len */
#define INDEFINITE_LEN ~0L
#ifdef USE_INDEF_LEN
#define BEncEocIfNec( b) BEncEoc (b)
#define BEncConsLen(b, len) 2 + BEncIndefLen (b)
#else
#define BEncEocIfNec( b)
#define BEncConsLen( b, len) BEncDefLen (b, len)
#endif
#define BEncIndefLen( b) ...
#define BEncDefLenTo127( b, len) ...
AsnLen BEncDefLen (BUF_TYPE b, AsnLen len);
AsnLen BDecLen (BUF_TYPE b, AsnLen *bytesDecoded, ENV_TYPE env);
#define BEncEoc( b) ...
#define BDEC_2ND_EOC_OCTET( b, bytesDecoded, env) ...
void BDecEoc (BUF_TYPE b, AsnLen *bytesDecoded, ENV_TYPE env);
\end{verbatim}
\end{small}
The {\C BDecLen} routine will report an error via {\C longjmp} if
it attempts to read past the end of the data or the decoded length is
too large to be held in the {\C AsnLen} representation.
{\C BDecEoc} will report an error if it attempts to read past the
end of the data or one of the EOC (End-Of-Contents) octets is
non-zero.
\section{\label{bool-C-section}BOOLEAN}
The BOOLEAN type is represented by an {\C unsigned char}. It has
the following routines for manipulating it.
\begin{small}
\begin{verbatim}
typedef unsigned char AsnBool;
AsnLen BEncAsnBool (BUF_TYPE b, AsnBool *data);
void BDecAsnBool (BUF_TYPE b, AsnBool *result, AsnLen *bytesDecoded,
ENV_TYPE env);
AsnLen BEncAsnBoolContent (BUF_TYPE b, AsnBool *data);
void BDecAsnBoolContent (BUF_TYPE b, AsnTag tag, AsnLen len,
AsnBool *result, AsnLen *bytesDecoded,
ENV_TYPE env);
#define FreeAsnBool( v)
void PrintAsnBool (FILE *f, AsnBool *b, unsigned short int indent);
\end{verbatim}
\end{small}
As discussed in Sections \ref{encode-gen-C-section} and \ref{decode-gen-C-section},
{\C BEncAsnBool} and {\C BDecAsnBool} encode/decode the UNIVERSAL
tag, length and content of the given BOOLEAN value. The\linebreak {\C BEncAsnBoolContent} and {\C BDecAsnBoolContent} routine only
encode/decode the content of the given BOOLEAN value.
The {\C FreeAsnBool} routine does nothing since the BOOLEAN type
does not contain pointers to data; the free routine generator does not
have to check which types need freeing and simply calls the type's
free routine. It also allows the user to modify the types and their
free routines without changing the free routine generator. However,
the ANY and ANY DEFINED BY type hash table initialization routine
generator does need to know which types have empty free routines
because the hash entries contain pointers to the free functions (NULL
is used for the empty free functions like {\C FreeAsnBool}). The
INTEGER, NULL, REAL and ENUMERATED types have empty free routines for
the same reason.
{\C BDecAsnBool} will report an error if the tag is not
UNIVERSAL-PRIM-1. {\C BDecAsnBoolContent} will report an error if it
decodes past the end of the data or the length of the encoded value
(given by the {\C len} parameter) is not exactly one octet.
\section{\label{int-C-section}INTEGER}
The INTEGER type is represented by a 32 bit integer type, {\C AsnInt}.
The C integer type chosen depends on the machine and compiler and may be {\C int}, {\C long} or {\C short}, whatever is 32 bits in size.
If you are using INTEGER types that are only positive (via subtyping or
protocol definition) you may want to use the {\C UAsnInt} and
associated routines that use the unsigned int for a larger positive value range.
\begin{small}
\begin{verbatim}
typedef int AsnInt;
typedef unsigned int UAsnInt;
AsnLen BEncAsnInt (BUF_TYPE b, AsnInt *data);
void BDecAsnInt (BUF_TYPE b, AsnInt *result, AsnLen *bytesDecoded,
ENV_TYPE env);
AsnLen BEncAsnIntContent (BUF_TYPE b, AsnInt *data);
void BDecAsnIntContent (BUF_TYPE b, AsnTag tag, AsnLen elmtLen,
AsnInt *result, AsnLen *bytesDecoded,
ENV_TYPE env);
#define FreeAsnInt( v)
void PrintAsnInt (FILE *f, AsnInt *v, unsigned short int indent);
AsnLen BEncUAsnInt (BUF_TYPE b, UAsnInt *data);
void BDecUAsnInt (BUF_TYPE b, UAsnInt *result, AsnLen *bytesDecoded,
ENV_TYPE env);
AsnLen BEncUAsnIntContent (BUF_TYPE b, UAsnInt *data);
void BDecUAsnIntContent (BUF_TYPE b, AsnTag tagId, AsnLen len,
UAsnInt *result, AsnLen *bytesDecoded,
ENV_TYPE env);
#define FreeUAsnInt( v)
void PrintUAsnInt (FILE *f, UAsnInt *v, unsigned short int indent);
\end{verbatim}
\end{small}
{\C BDecAsnInt} will report an error if the tag is not
UNIVERSAL-PRIM-2. {\C BDecAsnIntContent} will report an error if it
decodes past the end of the data or the integer value is too large for
an {\C AsnInt}.
\section{\label{null-C-section}NULL}
The NULL type is represented by the {\C AsnNull} type. Its content
is always empty and hence its encoded length always is zero.
\begin{small}
\begin{verbatim}
typedef char AsnNull;
AsnLen BEncAsnNull (BUF_TYPE b, AsnNull *data);
void BDecAsnNull (BUF_TYPE b, AsnNull *result, AsnLen *bytesDecoded,
ENV_TYPE env);
/* 'return' length of encoded NULL value, 0 */
#define BEncAsnNullContent(b, data) 0
void BDecAsnNullContent (BUF_TYPE b, AsnTag tag, AsnLen len,
AsnNull *result, AsnLen *bytesDecoded,
ENV_TYPE env);
#define FreeAsnNull( v)
void PrintAsnNull (FILE *f, AsnNull * b, unsigned short int indent);
\end{verbatim}
\end{small}
\section{\label{real-C-section}REAL}
The REAL type is represented by {\C AsnReal}, a double. This type's
representation can depend on the compiler or system you are using so
several different encoding routines are provided.
Even so, you may need to modify the code.
If you are using the REAL type in your ASN.1 modules, you should call the
{\C InitAsnInfinity()} routine to setup the {\C PLUS\_INFINITY}
and {\C MINUS\_INFINITY} values.
There are three encode routines included and they can be selected by
defining one of {\C IEEE\_REAL\_FMT}, {\C IEEE\_REAL\_LIB} or nothing.
Defining {\C IEEE\_REAL\_FMT} uses the encode routine that assumes the
double representation is the standard IEEE double \cite{68881}.
Defining {\C IEEE\_REAL\_LIB} uses the encode routine that assumes the
IEEE functions library (isinf, scalbn, signbit etc.\ ) is available.
If neither are defined, the default encode routine uses {\C frexp}.
There is only one content decoding routine and it builds the value
through multiplication and the {\C pow} routine (requires the math
library). The content decoding routine only supports the binary
encoding of a REAL, not the decimal encoding.
\begin{small}
\begin{verbatim}
typedef double AsnReal;
extern AsnReal PLUS_INFINITY;
extern AsnReal MINUS_INFINITY;
void InitAsnInfinity();
AsnLen BEncAsnReal (BUF_TYPE b, AsnReal *data);
void BDecAsnReal (BUF_TYPE b, AsnReal *result, AsnLen *bytesDecoded,
ENV_TYPE env);
AsnLen BEncAsnRealContent (BUF_TYPE b, AsnReal *data);
void BDecAsnRealContent (BUF_TYPE b, AsnTag tag, AsnLen len,
AsnReal *result, AsnLen *bytesDecoded,
ENV_TYPE env);
/* do nothing */
#define FreeAsnReal( v)
void PrintAsnReal (FILE *f, AsnReal *b, unsigned short int indent);
\end{verbatim}
\end{small}
{\C BDecAsnReal} will report an error if the value's tag is not UNIVERSAL-PRIM-9.
{\C BDecAsnRealContent} will report an error if the base is not supported or the decimal type REAL encoding is received.
\section{\label{bits-C-section}BIT STRING}
The BIT STRING type is represented by the {\C AsnBits} structure. It
contains a pointer to the bits and integer that holds the length
in bits of the BIT STRING\@.
In addition to the standard encode, decode, print and free routines,
there are some other utility routines. {\C AsnBitsEquiv} returns
TRUE if the given BIT STRINGs are identical. The {\C SetAsnBit},
{\C ClrAsnBit} and {\C GetAsnBit} are routines for writing and
reading a BIT STRING value.
You may notice that the AsnBits type does not have any means of
handling linked pieces of BIT STRINGs. Some ASN.1 tools use lists of
structures like {\C AsnBits} to represent BIT STRINGs. This is done
because, as you should be aware, BIT STRINGs can be encoded in a
nested, constructed fashion. The snacc BIT STRING decoder attempts to
save you the hassle of dealing with fragments of BIT STRINGs by
concatenating them in the decoding step. Every BIT STRING value
returned by the decoder will have contiguous bits.
Some people contend that fragmented BIT STRINGs are necessary to
support systems that lack enough memory to hold the entire value.
Snacc encodes value ``backwards'' so the entire value must be encoded
before it can be sent, thus you must have enough memory to hold the
whole encoded value. If the fragmented representation is useful to
your protocol implementation for other reasons, it should be fairly
simple to modify the BIT STRING routines. Remember, no significance
should be placed on where constructed BIT STRING values are fragmented.
Snacc uses a table to hold pointers to the BIT STRING fragments in the
buffer while it is decoding them. Once the whole BIT STRING value has
been decoded, a block of memory that is large enough to hold the
entire BIT STRING is allocated and the fragments are copied into it.
The table initially can hold pointers to 128 fragments. If more table
entries are needed the stack will grow via {\C realloc} (with
associated performance loss) and will not shrink after growing. If
you wish to modify this behaviour, change the
{\ufn \dots/c-lib/inc/str-stk.h} file.
The {\C FreeAsnBits} routine will free memory referenced by the
{\C bits} pointer.
\begin{small}
\begin{verbatim}
typedef struct AsnBits
{
int bitLen;
char *bits;
} AsnBits;
extern char numToHexCharTblG[];
#define TO_HEX( fourBits) (numToHexCharTblG[(fourBits) & 0x0f])
#define ASNBITS_PRESENT( abits) ((abits)->bits != NULL)
AsnLen BEncAsnBits (BUF_TYPE b, AsnBits *data);
void BDecAsnBits (BUF_TYPE b, AsnBits *result, AsnLen *bytesDecoded,
ENV_TYPE env);
AsnLen BEncAsnBitsContent (BUF_TYPE b, AsnBits *bits);
void BDecAsnBitsContent (BUF_TYPE b, AsnLen len, AsnTag tagId,
AsnBits *result, AsnLen *bytesDecoded,
ENV_TYPE env);
void FreeAsnBits (AsnBits *v);
void PrintAsnBits (FILE *f, AsnBits *b, unsigned short int indent);
int AsnBitsEquiv (AsnBits *b1, AsnBits *b2);
void SetAsnBit (AsnBits *b1, unsigned long int bit);
void ClrAsnBit (AsnBits *b1, unsigned long int bit);
int GetAsnBit (AsnBits *b1, unsigned long int bit);
\end{verbatim}
\end{small}
{\C BDecAsnBits} will report an error if the tag is not UNIVERSAL-CONS-3 or UNIVERSAL-PRIM-3.
When decoding constructed BIT STRING BER values, an error will be reported if a component other than the last one has non-zero unused bits in its last octet or an internal component does not have the UNIVERSAL-3 tag.
If the decoder attempts to read past the end of the data an error will be reported.
\section{\label{octets-C-section}OCTET STRING}
The OCTET STRING type is represented by the {\C AsnOcts} structure.
It contains a pointer to the octets and an integer that holds the length in octets of the OCTET STRING\@.
As with BIT STRINGs, OCTET STRINGs can have constructed values. These
are handled in the same way as the constructed BIT STRING values. The
decoded representation of an OCTET STRING is always contiguous.
The {\C FreeAsnOcts} routine will free the memory referenced by the
{\C octs} pointer. The {\C AsnOctsEquiv} routine will return TRUE
if the given OCTET STRINGs are identical.
\begin{small}
\begin{verbatim}
typedef struct AsnOcts
{
unsigned long int octetLen;
char *octs;
} AsnOcts;
#define ASNOCTS_PRESENT( aocts) ((aocts)->octs != NULL)
AsnLen BEncAsnOcts (BUF_TYPE b, AsnOcts *data);
void BDecAsnOcts (BUF_TYPE b, AsnOcts *result, AsnLen *bytesDecoded,
ENV_TYPE env);
AsnLen BEncAsnOctsContent (BUF_TYPE b, AsnOcts *octs);
void BDecAsnOctsContent (BUF_TYPE b, AsnLen len, AsnTag tagId,
AsnOcts *result, AsnLen *bytesDecoded,
ENV_TYPE env);
void FreeAsnOcts (AsnOcts *o);
void PrintAsnOcts (FILE *f, AsnOcts *o, unsigned short int indent);
int AsnOctsEquiv (AsnOcts *o1, AsnOcts *o2);
\end{verbatim}
\end{small}
{\C BDecAsnOcts} will report an error if the tag is not
UNIVERSAL-CONS-4 or UNIVERSAL-PRIM-4. When decoding constructed OCTET
STRING BER values, an error will be reported if an internal component
does not have the UNIVERSAL-4 tag. If the decoder attempts to read
past the end of the data an error will be reported.
\section{\label{oid-C-section}OBJECT IDENTIFIER}
In snacc, OBJECT IDENTIFIERs are kept in their encoded form to improve
performance. The {\C AsnOid} type is defined as {\C AsnOcts}, as
it holds the octets of the encoded OBJECT IDENTIFIER\@. It seems that
the most common operation with OBJECT IDENTIFIERs is to compare for
equality, for which the encoded representation (which is canonical)
works well.
There is a linked OBJECT IDENTIFIER representation called {\C OID}
and routines to convert it to and from the {\C AsnOid} format, but it
should not be used if performance is an issue.
Since the OBJECT IDENTIFIERs are represented {\C AsnOcts}, the
{\C AsnOcts} content encoding routine can be used for the
{\C AsnOid} content encoding routine. The other {\C AsnOcts}
encoding and decoding routines cannot be used because the OBJECT
IDENTIFIER has a different tag and cannot be encoded in a constructed
fashion.
An OBJECT IDENTIFIER must have a minimum of two arc numbers but the
decoding routines do not check this.
\begin{small}
\begin{verbatim}
typedef AsnOcts AsnOid;
#define ASNOID_PRESENT( aoid) ASNOCTS_PRESENT (aoid)
AsnLen BEncAsnOid (BUF_TYPE b, AsnOid *data);
void BDecAsnOid (BUF_TYPE b, AsnOid *result, AsnLen *bytesDecoded,
ENV_TYPE env);
#define BEncAsnOidContent(b, oid) BEncAsnOctsContent(b, oid)
void BDecAsnOidContent (BUF_TYPE b, AsnTag tag, AsnLen len,
AsnOid *result, AsnLen *bytesDecoded,
ENV_TYPE env);
#define FreeAsnOid FreeAsnOcts
void PrintAsnOid (FILE *f, AsnOid *b, unsigned short int indent);
#define AsnOidsEquiv( o1, o2) AsnOctsEquiv (o1, o2)
\end{verbatim}
\end{small}
\section{\label{list-C-section}SET OF and SEQUENCE OF}
The SET OF and SEQUENCE OF type are represented by the {\C AsnList}
structure. An {\C AsnList} consists of a head object that has
pointers to the first, current and last nodes and the current number
of nodes in the list. Each list node has a pointer to its next and
previous list member and the node's data. The first list node's
previous pointer is always NULL and the last list node's next pointer
is always NULL\@.
Each SET OF or SEQUENCE OF type is defined as an {\C AsnList}, so the
element type information (kept via a {\C void~*}) is not kept,
therefore, the {\C AsnList} type is not type safe.
The {\C AsnList} is a doubly linked list to simplify ``backwards''
encoding. The reverse link allows the list to be traversed in reverse
so the components can be encoded from last to first.
Initially, the lists were designed to allow the list element itself to
be contained in the list node (hence the {\C elmtSize} parameter to
the AsnListNew() routine). The design eventually changed such that
every list element was reference by pointer from the list node.
A small problem with the {\C AsnListNew} routine is the memory
allocation. Since it is used by the decoding routines to allocate new
lists, it uses whatever memory management you have setup with the
{\C Asn1Alloc} macro (see Section~\ref{lib-mem-C-section}). This may not be
desirable when building values to be transmitted. You may need to
provide another AsnListNew routine that uses a different allocation
scheme to solve this.
\begin{small}
\begin{verbatim}
typedef struct AsnListNode
{
struct AsnListNode *prev;
struct AsnListNode *next;
void *data; /* this must be the last field of this structure */
} AsnListNode;
typedef struct AsnList
{
AsnListNode *first;
AsnListNode *last;
AsnListNode *curr;
int count; /* number of elements in list */
int dataSize; /* space required in each node for the data */
} AsnList;
#define FOR_EACH_LIST_ELMT( elmt, list) ...
#define FOR_EACH_LIST_ELMT_RVS( elmt, list) ...
#define FOR_REST_LIST_ELMT( elmt, al) ...
#define CURR_LIST_ELMT( al) (al)->curr->data
#define NEXT_LIST_ELMT( al) (al)->curr->next->data
#define PREV_LIST_ELMT( al) (al)->curr->prev->data
#define LAST_LIST_ELMT( al) (al)->last->data
#define FIRST_LIST_ELMT( al) (al)->first->data
#define LIST_EMPTY(al) (( al)->count == 0)
#define CURR_LIST_NODE( al) ((al)->curr)
#define FIRST_LIST_NODE( al) ((al)->first)
#define LAST_LIST_NODE( al) ((al)->last)
#define PREV_LIST_NODE( al) ((al)->curr->prev)
#define NEXT_LIST_NODE( al) ((al)->curr->next)
#define SET_CURR_LIST_NODE( al, listNode) ((al)->curr = (listNode))
void AsnListRemove (AsnList *l);
void *AsnListAdd (AsnList *l);
void *AsnListInsert (AsnList *list);
void AsnListInit (AsnList *list, int dataSize);
AsnList *AsnListNew (int elmtSize);
void *AsnListPrev (AsnList *);
void *AsnListNext (AsnList *);
void *AsnListLast (AsnList *);
void *AsnListFirst (AsnList *);
void *AsnListPrepend (AsnList *);
void *AsnListAppend (AsnList *);
void *AsnListCurr (AsnList *);
int AsnListCount (AsnList *);
AsnList *AsnListConcat (AsnList *, AsnList *);
\end{verbatim}
\end{small}
There are a number of macros for dealing with the list type, the
most important being the list traversal macros. The
{\C FOR\_EACH\_LIST\_ELMT} macro acts like a ``for'' statment that
traverses forward through the list. The first parameter should be a
pointer to the list element type that will be used to hold the current list
element for each iteration of the ``for'' loop. The second parameter is
the list of elements that you wish to traverse.
The {\C FOR\_EACH\_LIST\_ELMT\_RVS} macro is identical to the
{\C FOR\_EACH\_LIST\_ELMT} macro except that is moves from the back of
the list to the front. The {\C FOR\_REST\_LIST\_ELMT} macro is
similar to the other two but it does not reset the {\C curr} pointer
in the {\C AsnList} type. This has the effect of iterating from the
current element to the end of the list. Look in the generated code
for a better indication of how to use these macros. The other macros
are straight forward.
\section{\label{any-C-section}ANY and ANY DEFINED BY}
The ANY and ANY DEFINED BY type are classically the most irritating
ASN.1 types for compiler writers. They rely on mechanisms outside of
ASN.1 to specify what types they contain. The 1992 ASN.1 standard has
rectified this by adding much stronger typing semantics and eliminating
macros.
The ANY DEFINED BY type can be handled automatically by {\em snacc} if
the SNMP OBJECT-TYPE \cite{snmp} macro is used to specify the
identifier value to type mappings. The identifier can be an INTEGER
or OBJECT IDENTIFIER\@. Handling ANY types properly will require
modifications to the generated code since there is no identifier
associated with the type.
The general approach used by {\em snacc} to handle ANY DEFINED BY
types is to lookup the identifier value in a hash table for the
identified type. The hash table entry contains information about the
type such as the routines to use for encoding and decoding.
Two hash tables are used, one for INTEGER to type mappings and the
other for OBJECT IDENTIFIER to type mappings. {\em Snacc} generates
an {\tt InitAny} routine for each module that uses the OBJECT-TYPE
macro. This routine adds entries to the hash table(s). The {\tt
InitAny} routine(s) is called once before any encoding or decoding is
done.
The hash tables are constructed such that an INTEGER or OBJECT
IDENTIFIER value will hash to an entry that contains:
\begin{itemize}
\item {the {\tt anyId}}
\item {the INTEGER or OBJECT IDENTIFIER that maps to it}
\item {the size in bytes of the identified data type}
\item {a pointer to the type's PDU encode routine}
\item {a pointer to the type's PDU decode routine}
\item {a pointer to the type's print routine}
\item {a pointer to the type's free routine}
\end{itemize}
The referenced encode and decode routines are PDU oriented in that
they encode the type's tag(s) and length(s) as well as the type's
content.
{\em Snacc} builds an {\tt enum} called {\tt AnyId} that enumerates
each mapping defined by the OBJECT-TYPE macros. The name of the value
associated with each macro is used as part of the enumerated
identifier. The {\tt anyId} in the hash table holds the identified
type's {\tt AnyId enum} value. The {\tt anyId} is handy for making
decisions based on the received identifier, without comparing OBJECT
IDENTIFIERs. If the identifiers are INTEGERs then the {\tt anyId} is
less useful.
With ANY DEFINED BY types, it is important to have the identifier
decoded before the ANY DEFINED BY type is decoded. Hence, an ANY
DEFINED BY type should not be declared before its identifier in a SET
since SETs are un-ordered. An ANY DEFINED BY type should not be
declared after its identifier in a SEQUENCE\@. {\em Snacc} will print a
warning if either of these situations occur.
The hash tables may be useful to plain ANY types which do not have an
identifier field like the ANY DEFINED BY types; the OBJECT-TYPE macro
can be used to define the mappings and the {\tt SetAnyTypeByInt} or
{\tt SetAnyTypeByOid} routine can be called with the appropriate
identifier value before encoding or decoding an ANY value. The
compiler will insert calls to these routines where necessary with some
of the arguments left as ``???''. There will usually be a ``{\tt /*
ANY -- Fix me! */}'' comment before code that needs to be modified to
correctly handle the ANY type. The code generated from an ASN.1
module that uses the ANY type will not compile without modifications.
OPTIONAL ANYs and ANY DEFINED BY types that have not been tagged are a
special problem for {\em snacc}. Unless they are the last element of a SET
or SEQUENCE, the generated code will need to be modified. {\em Snacc} will
print a warning message when it encounters one of these cases.
To illustrate how ANY DEFINED BY values are handled, we present
typical encoding and decoding scenarios. Each ANY or ANY DEFINED BY
type is represented in C by the {\tt AsnAny} type which contains only
a {\tt void *} named {\tt value} to hold a pointer to the value and a
{\tt AnyInfo *} named {\tt ai} which points to a hash table entry.
When encoding, before the ANY DEFINED BY value is encoded, {\tt
SetAnyTypeByOid} or {\tt SetAnyTypeByInt} (depending on the type of
the identifier) is called with the current identifier value to set the
{\tt AsnAny} value's {\tt ai} pointer to the proper hash table entry.
Then to encode the ANY DEFINED BY value, the encode routine pointed to
from the hash table entry is called with the {\tt value} {\tt void *}
from the {\tt AsnAny} value. The {\tt value} {\tt void *} in the {\tt
AsnAny} should point to a value of the correct type for the given
identifier, if the user set it up correctly. Note that setting the
{\tt void *} value is not type safe; one must make sure that the
value's type is the same as indicated by the identifier.
For decoding, the identifier must be decoded prior to the ANY DEFINED
BY value otherwise the identifier will contain an uninitialized value.
Before the ANY or ANY DEFINED BY value is decoded, {\tt
SetAnyTypeByOid} or {\tt SetAnyTypeByInt} (depending on the type of
the identifier) is called to set the {\tt AsnAny} value's {\tt ai}
pointer to the proper hash table entry. Then a block of memory of the
size indicated in the hash table entry is allocated, and its pointer
stored in the {\tt AsnAny} value's {\tt void *} entry. Then the decode
routine pointed to from the hash table entry is called with the newly
allocated block as its value pointer parameter. The decode routine
fills in the value assuming it is of the correct type. Simple!
There is a problem with {\em snacc}'s method for handling ANY DEFINED
BY types for specifications that have two or more ANY DEFINED BY types
that share some identifier values. Since only two hash tables are
used and they are referenced using the identifier value as a key,
duplicate identifiers will cause unresolvable hash collisions.
Here is some of the {\C AsnAny} related code from the header file. It
should help you understand the way things are done a bit better. Look
in the {\ufn hash.c} and {\ufn hash.h} files as well.
\begin{small}
\begin{verbatim}
/*
* 1 hash table for integer keys
* 1 hash table for oid keys
*/
extern Table *anyOidHashTblG;
extern Table *anyIntHashTblG;
typedef (*EncodeFcn) (BUF_TYPE b, void *value);
typedef void (*DecodeFcn) (BUF_TYPE b, void *value,
AsnLen *bytesDecoded, ENV_TYPE env);
typedef void (*FreeFcn) (void *v);
typedef void (*PrintFcn) (FILE *f, void *v);
/*
* this is put into the hash table with the
* int or oid as the key
*/
typedef struct AnyInfo
{
int anyId; /* will be a value from the AnyId enum */
AsnOid oid; /* will be zero len/null if intId is valid */
AsnInt intId;
unsigned int size; /* size of the C data type (ie as ret'd by sizeof) */
EncodeFcn Encode;
DecodeFcn Decode;
FreeFcn Free;
PrintFcn Print;
} AnyInfo;
typedef struct AsnAny
{
AnyInfo *ai; /* point to entry in hash tbl that has routine ptrs */
void *value; /* points to the value */
} AsnAny;
/*
* Returns anyId value for the given ANY type.
* Use this to determine to the type of an ANY after decoding
* it. Returns -1 if the ANY info is not available
*/
#define GetAsnAnyId( a) (((a)->ai)? (a)->ai->anyId: -1)
/*
* used before encoding or decoding a type so the proper
* encode or decode routine is used.
*/
void SetAnyTypeByInt (AsnAny *v, AsnInt id);
void SetAnyTypeByOid (AsnAny *v, AsnOid *id);
/*
* used to initialize the hash table(s)
*/
void InstallAnyByInt (int anyId, AsnInt intId,
unsigned int size, EncodeFcn encode,
DecodeFcn decode, FreeFcn free, PrintFcn print);
void InstallAnyByOid (int anyId, AsnOid *oid, unsigned int size,
EncodeFcn encode, DecodeFcn decode, FreeFcn free,
PrintFcn print);
/*
* Standard enc, dec, free, & print routines.
* for the AsnAny type.
* These call the routines referenced from the
* given value's hash table entry.
*/
void FreeAsnAny (AsnAny *v);
AsnLen BEncAsnAny (BUF_TYPE b, AsnAny *v);
void BerDecAsnAny (BUF_TYPE b, AsnAny *result, AsnLen *bytesDecoded,
ENV_TYPE env);
void PrintAsnAny (FILE *f, AsnAny *v, unsigned short indent);
/* AnyDefinedBy is the same as AsnAny */
typedef AsnAny AsnAnyDefinedBy;
#define FreeAsnAnyDefinedBy FreeAsnAny
#define BEncAsnAnyDefinedBy BEncAsnAny
#define BDecAsnAnyDefinedBy BDecAsnAny
#define PrintAsnAnyDefinedBy PrintAsnAny
\end{verbatim}
\end{small}
\section{\label{lib-buf-section}Buffer Management}
Encoding and decoding performance is heavily affected by the cost of
writing to and reading from buffers, thus, efficient buffer management
is necessary. Flexibility is also important to allow integration of
the generated encoders and decoders into existing environments. To
provide both of these features, the calls to the buffer routines are
actually macros that can be configured as you want (see
{\ufn \dots/c-lib/inc/asn-config.h}). Virtually all buffer calls will
be made from the encode/decode library routines. So macros used in
the generated code will make buffer calls.
If your environment uses a single, simple buffer type, the buffer
routine macros can be defined as the macros for your simple buffer type.
This results in the buffer type being bound at compile time, with no
function call overhead from the encode or decode routines. This also
means that the runtime library only works for that buffer type.
If multiple buffer formats must be supported at runtime, the buffer
macros can be defined like the ISODE buffer calls, where a buffer type
contains pointers to the buffer routines and data of the current
buffer type. This approach will hurt performance since each buffer
operation will be an indirect function call. I have implemented
buffers like this for the table tools (performace is already hosed so
slower buffer routines are a drop in the bucket). See the type tables
section for their description.
The backwards encoding technique requires special buffer primitives
that write from the end of the buffer towards the front. This
requirement will make it impossible to define buffer primitives that
write directly to stream oriented objects such as TCP connections. In
cases such as this, you must encode the entire PDU before sending it.
(Or else extend the back-end of the compiler to produce ``forwards''
encoders as well).
Nine buffer primitives are required by the runtime library's encode
and decode routines:
\begin{itemize}
\item {\C unsigned char BufGetByte (BUF\_TYPE b);}
\item {\C unsigned char BufPeekByte (BUF\_TYPE b);}
\item {\C char *BufGetSeg (BUF\_TYPE b, unsigned long int *lenPtr);}
\item {\C void BufCopy (char *dst, BUF\_TYPE b, unsigned long int *lenPtr);}
\item {\C void BufSkip (BUF\_TYPE b, unsigned long int len);}
\item {\C void BufPutByteRv (BUF\_TYPE b, unsigned char byte);}
\item {\C void BufPutSegRv (BUF\_TYPE b, char *data, unsigned long int len);}
\item {\C int BufReadError (BUF\_TYPE b);}
\item {\C int BufWriteError (BUF\_TYPE b);}
\end{itemize}
These buffer operations are described in the next subsections. The
{\C ExpBuf}, {\C SBuf} and {\C MinBuf} buffer formats that come
with the Snacc distribution and how to configure the buffer operations
are discussed following that.
\subsection{\label{buf-read-c-section}Buffer Reading Routine Semantics}
The buffer reading routines are called by the decoder routines. The
following is the list of necessary buffer reading routines and their
semantics. Be sure to setup the buffer in reading mode before
calling any of these routines. The means of putting a buffer in
reading mode depends on the buffer type.
\begin{verbatim}
unsigned char BufGetByte (BUF_TYPE b);
\end{verbatim}
Returns the next byte from the buffer and advances the current pointer
such that a subsequent buffer read returns the following byte(s).
This will set the read error flag if an attempt to read past the end
of the data is made.
\begin{verbatim}
unsigned char BufPeekByte (BUF_TYPE b);
\end{verbatim}
Returns the next byte from the buffer without advancing the current
pointer.
\begin{verbatim}
char *BufGetSeg (BUF_TYPE b, unsigned long int *lenPtr);
\end{verbatim}
Returns a pointer to the next bytes from the buffer and advances the
current pointer. {\C *lenPtr} should contain the number of bytes to
read. If the buffer has a least {\C *lenPtr} contiguous bytes
remaining to be read before calling {\C BufGetSeg}, a pointer to
them will be returned and {\C *lenPtr} will be unchanged. If there
are less than {\C *lenPtr} contiguous bytes remaining in the buffer
before the call to {\C BufGetSeg}, a pointer to them is returned and
{\C *lenPtr} is set to the actual number of bytes that are
referenced by the returned pointer. The current pointer will be
advanced by the value returned in {\C *lenPtr} (this may advance to the
next buffer segment if any). Note that the read error flag is not set
if {\C *lenPtr} is greater than the remaining number of unread
bytes.
\begin{verbatim}
unsigned long int BufCopy (char *dst, BUF_TYPE b, unsigned long int len)
\end{verbatim}
Copies the next {\C len} bytes from the buffer into the {\C dst char~*}
and advances the current pointer appropriately. Returns the
number of bytes actually copied. The number of bytes copied will be
less than requested only if the end of data is reached, in which case
the read error flag is set.
\begin{verbatim}
void BufSkip (BUF_TYPE b, unsigned long int len);
\end{verbatim}
Advances the buffer's current pointer by {\C len} bytes. This will set the
read error flag if less than {\C len} unread bytes remain in the
buffer before the call to {\C BufSkip}.
\begin{verbatim}
int BufReadError (BUF_TYPE b);
\end{verbatim}
Returns non-zero if a read error occurred for the given buffer.
Read errors occur if one of the buffer reading routines attempted to
read past the end of the buffer's data.
\subsection{\label{buf-write-c-section}Buffer Writing Routine Semantics}
Encoding routines call the buffer writing routines. Here is a list of
the buffer writing routine and their semantics. Before calling the
writing routines, you should make sure the buffer is setup for
writing in reverse mode. The means of doing this depends on the
buffer type.
\begin{verbatim}
void BufPutByteRvs (BUF_TYPE b, unsigned char byte);
\end{verbatim}
Writes the given byte to the beginning of the data in the given
buffer. The newly written byte becomes part of the buffer's data such
that subsequent writes place bytes before the newly written byte. If
a buffer write error occurs, subsequent writes do nothing.
\begin{verbatim}
void BufPutSegRvs (BUF_TYPE b, char *data, unsigned long int len);
\end{verbatim}
Prepends the given bytes, {\C data}, of length {\C len} to the
beginning of the data in the given buffer {\C b}. The {\C data}
bytes are written such that the first byte in {\C data} becomes the
first byte of the buffer's data, followed by the rest. (This means the
bytes in {\C data} are not reversed, they are simply prepended as a
unit to the buffer's original data). If a buffer write error occurs,
subsequent writes do nothing.
\begin{verbatim}
int BufWriteError (BUF_TYPE b);
\end{verbatim}
Returns non-zero if a write error occurred for the given buffer.
Write errors occur if the buffer runs out of space for data or cannot
allocate another data block (depends on the buffer type).
\subsection{Buffer Configuration}
The runtime library's encode and decode routines as well as the
generated code access the buffers via the nine buffer macros
described in the last two sections. These macros can be defined to
call simple macros for speed or to call functions. Note that the
buffer configuration is bound at the time the library and generated
code are compiled.
The following is from {\ufn \dots/include/asn-config.h} and shows how to
configure the buffer routines. This setup will make all calls to
{\C BufGetByte} in the library and generated code call your
{\C ExpBufGetByte} routine; the other buffer routines are mapped to
their {\C ExpBuf} equivalents in a similar way.
\begin{verbatim}
#include "exp-buf.h"
#define BUF_TYPE ExpBuf **
#define BufGetByte( b) ExpBufGetByte (b)
#define BufGetSeg( b, lenPtr) ExpBufGetSeg (b, lenPtr)
#define BufCopy( dst, b, lenPtr) ExpBufCopy (dst, b, lenPtr)
#define BufSkip( b, len) ExpBufSkip (b, len)
#define BufPeekByte( b) ExpBufPeekByte (b)
#define BufPutByteRv( b, byte) ExpBufPutByteRv (b, byte)
#define BufPutSegRv( b, data, len) ExpBufPutSegRv (b, data, len)
#define BufReadError( b) ExpBufReadError (b)
#define BufWriteError( b) ExpBufWriteError (b)
\end{verbatim}
If you want to use your own buffer type, simply edit the
{\ufn asn-config.h} file such that it includes your buffer's header
file, sets the {\C BUF\_TYPE} type, and defines the nine buffer
routines ({\C BufGetByte} etc.) to call your buffer routines. Your
buffer routines should have the semantics and prototypes described in
the last two sections (Sections \ref{buf-read-c-section} and~\ref{buf-write-c-section}).
\subsection{ExpBuf Buffers}
The {\C ExpBuf} buffers are a doubly linked series of buffers that
can be expanded when encoding by adding new buffers as necessary.
Each {\C ExpBuf} consists of two blocks of memory, one for the
control and linking information and the other for the data; when
refering to an {\C ExpBuf} both parts are included. {\C ExpBuf} is
short for ``Expanding Buffer''. Look in {\ufn \dots/c-lib/exp-buf.c}
for an ASCII drawing of the {\C ExpBuf} buffers. Take a look a the
{\ufn \dots/c-examples/simple/expbuf-ex.c} file for a quick
introduction on how to use {\C ExpBufs}.
{\C ExpBufs} are fairly general and useful when a reasonable upper
bound can not be put on the size of the encoded values that will be
encountered by the protocol. The flexibility of these buffer routines
will hurt the performance as many of the {\C ExpBuf} calls are not
macros and new buffers may need to be allocated during encoding.
For encoding you need to write into the {\C ExpBufs}. Start with a
single ExpBuf (or the last one in a list of ExpBufs from a previous
encoding). Make sure this ExpBuf has been reset is ``Write Reverse''
mode (use {\C ExpBufResetInWriteRvsMode}). This clears the write
error flag (and sets the read error flag in case you try a read) and
resets the data start and data end pointers such that the buffer is
empty and ready for writing from the end towards the front.
During encoding, if an {\C ExpBuf}'s data part fills up, a new
{\C ExpBuf} before (since writing is reversed) the current buffer is
needed. If the {\C prev} pointer in the current buffer is non-NULL,
the previous buffer is reset for writing and becomes the current
buffer. If the {\C prev} pointer in the current buffer si NULL, a new
buffer is allocated, its pointer is placed in {\C prev} and it
becomes the current buffer. The notion of current buffer is handled
by the parameter to the encoding and decoding routines. The buffer
parameter is an {\C ExpBuf~**} and it always holds the current
{\C ExpBuf~*} (current buffer).
When encoding is finished and the encoded value has been transmitted,
you have two options. You can free the entire buffer list or you can
keep them around and re-use them for the next encoding. Freeing the
buffers after each encoding may be quite slow. If you re-use the
buffers, the buffer list will grow to the size of the largest encoding
and stay there. You can easily implement other management schemes.
By default the {\C ExpBuf}s (both parts) are allocated and freed with
{\C malloc} and {\C free}; you may want to change this to fit your
environment better. If buffer allocation fails during a write, the
writeError flag will be set and subsequent writes will do nothing.
For decoding you will want to put the encoded data into the
{\C ExpBuf} format. For example, if your encoded value is
contiguous in a single block of memory, you could use
{\C ExpBufInstallDataInBuf} to attach your data to a single ExpBuf.
Once your data is in the ExpBuf format, you should call
{\C ExpBufResetInReadMode} on the first buffer in the list (if more
than one). Then you can pass it to the desired decode routine.
If a decode routine attempts to read past the end of a buffer (usually
due to an erroneous encoding), the readError flag will be set for the
current {\C ExpBuf} in the list. This error will typically cause
the decoding routine that called the buffer read routine to call
{\C longjmp}.
The {\C BUF\_TYPE} is defined as {\C ExpBuf~**} so that the buffer
parameter {\C b} can be set to the next active {\C ExpBuf} by the
buffer routines. This saves having a head of the list type structure
that keeps track of the first, last and current buffers (the
indirectness of this approach would hurt performance).
There are many routines for administrating the {\C ExpBufs} if you
want to treat them like an abstract data type. Sometimes it may be
easier to skip the utility routines and modify the fields directly.
The following routines are the required nine buffer routines. Compile
the library and the generated code with the {\C USE\_EXP\_BUF} symbol
defined to map buffer routines that the generated and library code
calls to the {\C ExpBuf} routines (see
{\ufn \dots/c-lib/inc/asn-config.h}). These {\C ExpBuf} routines
adhere to the buffer routine prototypes and semantics defined in
Sections \ref{buf-read-c-section} and~\ref{buf-write-c-section}.
\begin{verbatim}
void ExpBufSkip (ExpBuf **, unsigned long len);
int ExpBufCopy (char *dst, ExpBuf **b, unsigned long len);
unsigned char ExpBufPeekByte (ExpBuf **b);
char *ExpBufGetSeg (ExpBuf **b, unsigned long *len);
void ExpBufPutSegRvs (ExpBuf **b, char *data, unsigned long len);
unsigned char ExpBufGetByte (ExpBuf **b);
void ExpBufPutByteRvs (ExpBuf **b, unsigned char byte);
#define ExpBufReadError( b) ((*b)->readError)
#define ExpBufWriteError( b) ((*b)->writeError)
\end{verbatim}
The following {\C ExpBuf} routines are also provided. Their
descriptions can be found in the code.
\begin{verbatim}
void ExpBufInit (unsigned long dataBlkSize);
void ExpBufInstallDataInBuf (ExpBuf *b, char *data, unsigned long int len);
void ExpBufResetInReadMode (ExpBuf *b);
void ExpBufResetInWriteRvsMode (ExpBuf *b);
ExpBuf *ExpBufAllocBufAndData();
void ExpBufFreeBufAndData (ExpBuf *b);
void ExpBufFreeBufAndDataList (ExpBuf *b);
ExpBuf *ExpBufNext (ExpBuf *b);
ExpBuf *ExpBufPrev (ExpBuf *b);
ExpBuf *ExpBufListLastBuf (ExpBuf *b);
ExpBuf *ExpBufListFirstBuf (ExpBuf *b);
int ExpBufAtEod (ExpBuf *b);
int ExpBufFull (ExpBuf *b);
int ExpBufHasNoData (ExpBuf *b);
char *ExpBufDataPtr (ExpBuf *b);
unsigned long ExpBufDataSize (ExpBuf *b);
unsigned long ExpBufDataBlkSize (ExpBuf *b);
\end{verbatim}
\subsection{SBuf Buffers}
The {\C SBuf}s are simple buffers of a fixed size, much like an
{\C ExpBuf} that cannot expand. If you attempt to write
past the end of the buffer, the writeError flag will be set and the
encoding will fail. If you attempt to read past the end of a buffer
the readError flag will be set and the decoding will fail.
The {\C SBuf}s are useful if you can put a reasonable upper bound on
the size of the encodings you will be dealing with. The buffer
operations are much simpler because the data is contiguous. In fact,
all of the {\C SBuf} buffer operations are implemented by macros.
Look in {\ufn \dots/c-examples/simple/sbuf-ex.c} for a quick
introduction to using {\C SBuf}s in your code. The following
operations are defined for the {\C SBuf} buffers.
\begin{verbatim}
/* The nine required buffer operations */
#define SBufSkip(b, skipLen) ...
#define SBufCopy(dst, b, copyLen) ...
#define SBufPeekByte(b) ...
#define SBufGetSeg( b, lenPtr) ...
#define SBufPutSegRvs(b, seg, segLen) ...
#define SBufGetByte(b) ...
#define SBufPutByteRvs(b, byte) ...
#define SBufReadError(b) ...
#define SBufWriteError(b) ...
/* other useful buffer operations */
#define SBufInit(b, data, dataLen) ...
#define SBufResetInReadMode(b) ...
#define SBufResetInWriteRvsMode(b) ...
#define SBufInstallData(b, data, dataLen) ...
#define SBufDataLen(b) ...
#define SBufDataPtr(b) ...
#define SBufBlkLen(b) ...
#define SBufBlkPtr(b) ...
#define SBufEod(b) ...
\end{verbatim}
Snacc is configured to use {\C SBuf}s by default. The symbols that
will affect the buffer configuration during compilation of the
libraries and generated code are {\C USE\_EXP\_BUF} and
{\C USE\_MIN\_BUF}.
\subsection{MinBuf Buffers}
The {\C MinBuf}s provide maximum performance but should only be used under
restricted conditions (to avoid segmentation faults etc.). No checks are
made to determine whether a decoder is reading past the end of the
buffer or if an encoder is writing ``past'' the beginning of the data
block (remember, snacc encoders write backwards).
A {\C MinBuf} is just a {\C char~**}; the referenced {\C char~*} points
to the next byte to be read or the last byte that was written. The
read routine advances the {\C char~*} and the write reverse routines
move the {\C char~*} backwards.
When you start encoding, the {\C MinBuf} {\C char~**} should be a
pointer to a pointer to the byte AFTER the last valid byte in your
buffer. For example the following C fragment would work:
\begin{verbatim}
PersonnelRecord pr;
char blk[128];
char *minBuf;
minBuf = blk + 128; /* start writing a end of block */
BEncPersonnelRecord (&minBuf, pr);
\end{verbatim}
The {\C MinBuf}s should only be used during encoding if the size of
the {\C MinBuf}'s buffer is guaranteed to be large enough to hold
the encoded value. Otherwise, the encoder will blindly continue
writing into whatever lies after the {\C MinBuf}'s buffer.
When you start decoding, the {\C MinBuf} value should be a pointer
to a pointer to the first byte of the BER value to be decoded. Look
in {\ufn \dots/c-examples/simple/minbuf-ex.c} for a real example.
The {\C MinBuf}s should only be used for decoding when the value
being decoded is certain to contain no encoding errors. Otherwise, for
encodings that are incomplete or contain length errors, the decoder may
attempt to read the memory that follows the {\C MinBuf}s. If you are
lucky, the decoder will return an error with the {\C longjmp}
mechanism. If your system has memory protection and you are unlucky
this may abort your program. If you are really unlucky, the data
following the {\C MinBuf} may fool the decoder into thinking that it
is valid and you receive a wrong PDU with no error indication. This
risky technique has been used successfully in some systems where the
encodings are not guaranteed to be correct.
To configure the generated code to use the {\C MinBuf}s, compile it
with the {\C USE\_MIN\_BUF} symbol defined.
\subsection{Hybrid Buffer Solutions}
The decoding routines only call the buffer reading routines and the
encoding routines only call the buffer writing routines. You may wish
to choose a different buffer format for the encoding and decoding to
gain performance. For instance, if you can be sure that the size of
outgoing encodings is less than a certain upper bound, but don't want
to risk segmentation faults when decoding incoming values, you could
use {\C MinBuf}s for the the buffer writing (encoding) operations
and {\C SBuf}s or {\C ExpBuf}s for the buffer reading (decoding)
operations.
In this case you will need to massage the generated code to achieve
the desired results.
\section{\label{lib-mem-C-section}Dynamic Memory Management}
Like buffer management, efficient memory management is very important
for efficient decoders. As a decoder decodes a value, it allocates
memory to hold the internal representation of the value.
The runtime librarys and the generated decode routines allocate memory
using the\linebreak {\C Asn1Alloc} routine. The runtime librarys
and the generated free routines free memory using the {\C Asn1Free}
routine. The decoding routines also use {\C CheckAsn1Alloc} to make
sure that each allocation succeeded. These memory routines are defined
in the
{\ufn asn-config.h} and have the prototypes:
\begin{verbatim}
void *Asn1Alloc (unsigned long int size);
void Asn1Free (void *ptr);
int CheckAsn1Alloc (void *ptr, ENV_TYPE env);
\end{verbatim}
The decoders assume that {\C Asn1Alloc} returns a \emph{zeroed} block
of memory. This saves explicit initialization of OPTIONAL elements with
NULL in the generated decoders. It wouldn't be too hard to modify the
compiler to produce decoders that initialized OPTIONAL elements
explicitly.
The generated free routines hierarchically free all a value's
memory using a depth first algorithm. If you use the Nibble Memory
scheme, you will not need the generated free routines.
By default, snacc uses a ``Nibble Memory'' scheme to provide efficient
memory management. Nibble Memory works by allocating a large block of
memory for allocating from. When the decoded value has been
processed, you can free the entire value by calling a routine that
simply resets a few pointers. There is no need to traverse the entire
value freeing a piece at a time. The following is from
{\ufn nibble-alloc.h}.
\begin{verbatim}
void InitNibbleMem (unsigned long int initialSize,
unsigned long int incrementSize);
void *NibbleAlloc (unsigned long int size);
void ResetNibbleMem();
void ShutdownNibbleMem();
\end{verbatim}
You must explicitly initialize the Nibble Memory with the
{\C InitNibbleMem} routine before using a decoder. You must specify
the initial size of the nibble block and the size that it should grow
by. If you attempt to allocate a block that is larger that the
initial nibble block or its grow size, a new block of the correct size
will be allocated. Note that the ``growth'' occurs by linking
separate blocks, not by the potentially slow alternative,
{\C realloc}.
When you have processed the decoded value you can free it by calling
{\C ResetNibbleMem}. This resets a couple pointers and frees any
extra blocks that were allocated to handle values larger than the
initial block size. The original memory block is zeroed
using {\C memset} so that all allocations will return zeroed values.
This is necessary to support the implicit initialization of OPTIONAL
elements to NULL\@. The zeroing is done in this routine instead of
{\C NibbleAlloc} under the assumption that zeroing one large block
is more efficient than zeroing pieces of it as they are allocated.
When you no longer need the Nibble Memory, you can release it by
using\linebreak {\C ShutDownNibbleMem}. This frees all of the
memory associated with Nibble Memory, both the control data and the
block(s) used for allocation.
There are some problems with this memory management scheme. Currently
the Nibble Memory control information is kept track of via a global
variable that holds a pointer to the control information. This can
present a problem if separate Nibble Memory contexts are needed, for
example, one to hold one value that will be kept after decoding and
another to hold a decoded value that will soon be discarded.
The problem of separate contexts could be solved by adding another
layer that would use identifiers for different memory contexts. This
would require you to set the context using its identifier before
calling a decoding routine and to pass the context identifier to the
{\C ResetNibbleMem} routine.
Another problem has to do with building the values to be encoded.
There is no restriction on what allocator you use to build internal
values. However, it is convenient to use the {\C AsnListNew}
routine to allocate and initialize a list type. Unfortunately,
{\C AsnListNew} is used by the decoding routines so it uses the
{\C Asn1Alloc} routine to allocate the new list. You should be
aware of this if {\C Asn1Alloc} is not what you are using to
allocate the rest of the value. This could be fixed with a different
interface to the {\C AsnListNew} routine.
It is possible to change the memory management system without too much
difficulty. For example if you are not too worried about performance
and want to use {\C malloc} and {\C free}, you could change the
{\ufn asn-config.h} file as follows:
\begin{verbatim}
#include "malloc.h"
#define Asn1Alloc( size) calloc (1, size)
#define Asn1Free( ptr) free (ptr)
#define CheckAsn1Alloc( ptr, env)\
if ((ptr) == NULL)\
longjmp (env, -27);
\end{verbatim}
If you use {\C malloc} based allocators such as {\C calloc}, you
must use the generated free routines to free your values. Note that
this example used {\C calloc} instead of {\C malloc} because
{\C calloc} {\em zeroes} each allocated block of memory, as required
by the decoders.
\section{\label{lib-err-C-section}Error Management}
The decoding routines use {\C longjmp} to handle any errors they
encounter in the value being decoded. {\C longjmp} works by rolling
back the stack to where the {\C setjmp} call was made. Every decode
routine takes a {\C jmp\_buf env} parameter (initialized by the
{\C setjmp} call) that tells the {\C longjmp} routine how to
restore the processor to the correct state. {\C longjmp} makes the
error management much simpler since the decoding routines do not have
to pass back error codes or check ones from other decoding routines.
Before a PDU can be decoded, the {\C jmp\_buf env} parameter to the
decoding routine must be initialized using the {\C setjmp} routine.
This should be done immediately and only once before calling the
decoding routine. This parameter will be passed down to any other
decoding routines called within a decoding routine. The following code
fragment from {\ufn \dots/c-examples/simple/exbuf-ex.c} shows how to
use {\C setjmp} before decoding.
\begin{small}
\begin{verbatim}
if ((val = setjmp (env)) == 0)
BDecPersonnelRecord (&buf, &pr, &decodedLen, env);
else
{
decodeErr = TRUE;
fprintf (stderr, "ERROR - Decode routines returned %d\n", val);
}
\end{verbatim}
\end{small}
The code that will signal an error typically looks like:
\begin{small}
\begin{verbatim}
if (mandatoryElmtCount1 != 2)
{
Asn1Error ("BDecChildInformationContent: ERROR - non-optional elmt missing from SET.\n");
longjmp (env, -108);
}
\end{verbatim}
\end{small}
Most {\C longjmp} calls are preceded by a call to {\C Asn1Error}
which takes a single {\C char~*} string as a parameter. The library
routines and the generated code try to use meaningful messages as the
parameter. {\C Asn1Error} is defined in {\ufn \dots/c-lib/inc/asn-config.h} and
currently just prints the given string to {\C stderr}. You may wish
to make it do nothing, which may shrink the size of your binary
because all of the error strings will be gone. {\C Asn1Warning} is
similar but is not used by the library or generated code anymore.
The encoding routines do no error checking except for buffer
overflows. Hence, they do not use the {\C longjmp} mechanism and
instead require you to check the status of the buffer after encoding
(use {\C BufWriteError()}). If you are not building your values
properly, for example having random pointers for uninitialized
OPTIONAL elements, the encode routines will fail, possibly
catastrophically.
|