C H A P T E R  9

image

C-Style Data Types

Working with data types in packetC introduces some interesting dynamics that will initially require care and extra thought by C developers from time to time given some of the restrictions, mostly due to strong type enforcement. The notion of casting and the strong casting rules is imperative to ensuring code works as expected. Packets are modeled as arrays of bytes and working with portions of packets is essential to making sense of the data. In packetC, the notion of an array slice was introduced to allow for direct access to portions of byte arrays without the need for pointers and for keeping them in line with strong type enforcement. This also applies to complex structures and unions that can be copied or, better yet, cast back and forth to byte arrays, providing multiple ways to view data elements, depending on what is most convenient for the programmer.

Enumeration Types

packetC enumeration types map a series of unsigned integer values to a corresponding set of identifier names that are enumerated in the type declaration. A name is associated with a user-specified value by following the name with an equal sign and an unsigned integer value. Default values are mapped to the identifiers in a left-to-right manner. If the user does not specify a value for the leftmost name, it defaults to a value of zero. Any subsequent name that lacks a user-specified value receives a value equal to one plus the value of the name immediately to its left. It is an error for two or more names to have the same value. Enumerated type declarations specify one of packetC's integer types as a base type. It is an error to declare an enumeration value too large for the specified base type to store. Unlike C, packetC defines equality, relational, and a simple assignment operator for enumerated types but not arithmetic or bitwise operators.

// use default values 0, 1, 2
enum int        StorageType { BYTE_TYPE, SHORT_TYPE, INT_TYPE };
// user values, 32, 64, 128
enum byte       MySize { SINGLE = 32, DOUBLE = 64, QUAD = 128 };
// values 0, 63, 256
enum short      Alert { BAD_STACK, MFG1 = 63, MFGR2 = 256 };
// use default values 0, 1, 2
enum byte       StorageType { BYTE_TYPE, SHORT_TYPE, INT_TYPE };

// use default values 0, 1, 2, 3, 4, 5, 6
enum long       Day1 { MON, TUE, WED, THUR, FRI };
enum short      Day2 { SAT, SUN };

// user values, 32, 64, 128
enum byte       MySize { SINGLE = 32, DOUBLE = 64, QUAD = 128 };

// values 0, 63, 256
enum short      Alert { BAD_STACK, MFG1 = 63, MFGR2 = 256 };

// ERROR: redundant values in 0, 1, 2, 1
enum int Color { RED, GREEN, BLUE, CYAN =1};

Although packetC stores enumeration types in integer base types, each enumeration type defines a distinctive type. Thus, identifiers and values with one given enumeration type cannot be combined in assignments or expressions with identifiers and values that have some other enumeration type or the base type of the enumerated type. Trying to assign an enumeration type variable a value that is not associated with one of the enumerated names is an error. The following example uses the types defined above to show legal and illegal usage.

// Using enums defined in examples above
StorageType i1, i2 = BYTE_TYPE;
MySize m1;
int j;
i1 = i2;                  // both have same type

// legal; can cast an enumerated type to an
// integer type big enough to hold it.
j  =  (int) i1;

// All of these are errors
i1 = (StorageType) j;    // ERROR: cannot cast int to enumerated type
i1 = m1;                 // ERROR: variables have different enum types.

The following example shows enumerations in use:

packet module declenum;

enum byte StorageType { BYTE_TYPE, SHORT_TYPE, INT_TYPE, LONG_TYPE };
enum long  Day1 { MON, TUE, WED, THUR, FRI };
enum short Day2 { SAT, SUN };

enum byte MySize { SINGLE = 32, DOUBLE = 64, QUAD = 128 };
enum short Alert { BAD_STACK, MFG1 = 63, MFG2 = 256 };
enum int Color { RED, GREEN, BLUE, CYAN = 5 };

int pass_, fail_;
%pragma control pass_ (export);
%pragma control fail_ (export);

byte result_[4];

void main( $PACKET pkt, $PIB pib, $SYS sys ) {
  int statusOffset;
  statusOffset = pib.payloadOffset;
  StorageType i1, i2 = BYTE_TYPE;
  MySize m1;
  int j;

  i1 = i2;
  j = (int)i1;

  if ( j != 0 ) {
     fail_++;
     result_[0]='F'; result_[1]='A'; result_[2]='I'; result_[3]='L';
  } else {
     pass_++;
     result_[0]='P'; result_[1]='A'; result_[2]='S'; result_[3]='S';
  }
  pkt[statusOffset:statusOffset+3] = result_[0:end];
  pib.action = FORWARD_PACKET;
}

Arrays

packetC supports one- and two-dimensional array types that use byte, short, int, or long types as their base type. These types use zero-based dimensions. With the exception of dimension and base type restrictions, packetC practices follow those of C99 for declaring and using arrays.

byte barr[5];           // barr is an array [0..4] of byte
int  iarr[4][3];        // iarr is an array [0..3] of array [0..2] of int
long cnt[10];           // cnt is an array [0..9] of long

// Errors
int  carr3[2][3][2];    // ERROR: more than 2 dimensions
barr [9] = 5;           // ERROR: legal zero-based indices are 0..4

A two-dimensional array is an array with a base type that is, itself, an array. Two-dimensional arrays can be composed either by a single array type declaration or by combining related declarations.

typedef  int T4[3];
T4  dArr[4];            //  dArr is array [0..3] of array [0..2] of int

// each row has 1, 2, 3 in the columns
int y[4][3] = { 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3 };
// same as above, broken out rows
int y[4][3] = { {1, 2, 3}, {1, 2, 3}, {1, 2, 3}, {1, 2, 3} };
images

Figure 9-1. Memory allocation for array packetContents

Allocation of memory is predictable in packetC. The array packetContents is defined below and its memory allocations are shown in Figure 9-1.

int packetContents[3][4] = {

{12, 13, 14, 15},
{20, 21, 22, 23},
{35, 45, 55, 65}
};

The packetContents two-dimensional array above is equivalent to the one-dimensional array declaration below:

int packetContents[12] = {12, 13, 14, 15,20, 21, 22, 23, 35, 45, 55, 65};

In C, structures can be the element type of an array. This is not legal in packetC given that it would impact array subscripting practices.

Array Subscripting Operator

A postfix expression that identifies an array, followed by an expression within square brackets, designates an element of the array. Unlike C99, packetC does not indicate internal array addresses or the starting storage address of an array with array references that have fewer subscripts than the array has dimensions. Similarly, the user should not presume a specific, contiguous array storage layout. See the section on arrays within the Data Types section. Any expression that produces an integer result that is within the array's legal range can serve as a legal array subscripting expression. Subscripting operators are only legal in conjunction with packetC array or database variables.

short   sa[3] = { 5, 6, 7 }, sb;
byte ba = 1, bb = 2;
sb = sa[2];          // sb = 7

sb = sa[ bb – ba ];    // sb = 6

Unsized Dimensions

Variables can be declared with unsized array dimensions but each dimension must be unambiguously sized by an accompanying initialization clause. When a multiple-dimension array has one or more unsized dimensions, the initialization clause must use a nested literal form that unambiguously and consistently specifies the size of all unsized dimensions. Unlike C, a multidimensional array with an unsized dimension:

  • Is not restricted to allowing only the leftmost dimension to be unsized
  • Must use a nested literal form for the initialization clause
int  aarr[]    = {1,2,3};                   // legal, aarr [3]
int  barr[2][] = { {1,2}, {3,4}};           // legal, barr [2][2]
int  carr[][3] = {{1,2,3}, {3,4,5}};        // legal, carr [2][3]
int  darr[][3] = {1,2,3,4,5,6};             // ERROR: not nested literal
int  earr[][]  = {{1,2,3}, {3,4,5}};        // legal, earr [2][3]
int  farr[][]  = {1,2,3,4,5,6};             // ERROR: ambiguous
int  garr[][]  = {{1,2}, {3,4,5}};          // ERROR: inconsistent

Array Assignment

Unlike the C language, packetC does not permit array references with an incomplete set of indices as a mechanism for denoting array or sub-array starting addresses. The absence of this practice is in the interest of packetC's emphasis on security and reliability. packetC uses the name of an array without any accompanying indices to indicate the entire array. Thus, all the element values of a destination array can be set equal to those of a source array if the two arrays have identical dimensions and the same base type. Only the simple assignment operator is defined for entire arrays acting as operands.

typedef byte TempArray26[2][6];
TempArray26  a, b;
byte  c[3][4], d[6], e[8];

a = b;                // legal
d[0:4] = e[1:5];      // legal

// Errors
c = a;              // ERROR: operands not of same type
b = a[1];           // ERROR: Using a[1] does not imply a[1][0:end]. This is illegal.
d = a[1];           // ERROR: illegal.
d[1] = a[1][0:end]; // legal

The array assignment implements the equivalent of a type-safe memcopy() operation in packetC. For those areas where one would normally desire a memcopy(), array assignment comes into play.

Array Slicing

A portion of an array can be returned by array slicing. A special keyword called end can be used to specify the greatest legal index value of an array. Array slicing is the method where a range, or slice, of an array is specified by providing a start and stop offset separated by a colon.

byte  a[16];
byte  b[8];
b[0:end] = a[0:7];

A set of contiguous array elements is a packetC array slice. The size of a slice is specified with a range expression, which specifies the lowest and highest index values of the slice in left-to-right form.

Legal ranges have a left-hand expression that is less than or equal to the right-hand expression. Both indices must be within the array's bounds. The end keyword can be used as the right-hand expression to indicate the greatest legal index value for that particular array. A range that describes a single array element is legal (i.e., the two sides of the range are equal) if the element is within the array's bounds. A slice can only describe a range for the array dimension with the index that varies most rapidly. Slices can be defined for arrays of any integer base type. In general, array slices can be used in the same syntactic and semantic situations where an entire array could legally be used.

byte  a[16];
byte  b[8];
byte  c[256][8];
byte left = 0, right = 7;

// legal: variables within range
b[ left : right ] = a[ left : right ];
b[ left : right ] = pkt [left : right ];

// legal: constants and operations in range
b[ left : 3 ] = a[ right + 1 : right + 4 ];

// legal with cast (1-element slice is a 1-element array)
byte bscalar;
bscalar = (byte)b[0:0];

// ERROR: cannot directly assign scalar to an array/slice
b[ 0 : 0 ] = a[12];

// legal: assigning slices/arrays with same base type and size
b[ 0 : 0 ] = a[12:12];
b[ 0 : 4 ] = a[4:8];

// legal: 2D array, range is rightmost dimension
b[ 0 : right ] = c[5][0:7];

// legal: assigning 2 8-element slices
b[ 0 : end ] = c[4][0:end];

// ERROR: least rapidly varying index has range
a[ 0 : 15 ]= c[0:1][0:7];

// legal: assigning slices/arrays with same base type (byte) and size
// The packet (pkt) is able to be treated as an array of byte
b[ 0 : 4 ] = pkt[4:8];

Range expressions are not limited to constants. However, a range expression for an array slice that is being type-cast must be constant. A range expression that can only be determined at run time and that yields out of bounds or otherwise illegal range values shall trigger a system-defined response for that state array indexing error state. (See the section on System-Defined Response in Chapter 13).

Array Initialization

Above, the notion of assigning two arrays or two equivalent slices of arrays was shown. This presented a type-safe method of copying blocks of memory of dynamic sizes. Another such instance that is often useful is to set all elements of an array to a particular value, much like the calling of a memset() function in C. As there are no pointers and type-safety is critical, packetC implements two means for providing this functionality. Both revolve around the repetition operator, #, which can occur in the data set on the right-hand side of the statement or as a part of a compound assignment operator, #=.

typedef byte TempArray26[2][6];
TempArray26 a, b;
byte c[3][4], d[6], e[8];

byte f[1000] = { 0, 1, 2, 3, 255#996 };   // Fills last 996 elements with value of 255

a #= 80;           // Assigns every byte of a to contain a value of 80.
d[0:4] #= e[1];    // Where e[1] is a byte that has its value assigned to 0 through 4 in d

// Errors
d #= e[0:2];       // ERROR: e[0:2] is not of same type, byte, as elements of d.

The array assignment using the repetition compound operator implements the equivalent of type safe memset() operation in packetC. For those areas where one would normally desire a memset(), this unique-to-packetC compound operator assignment comes into play.

Structures and Unions

Complex structures are supported. Fields can be accessed on the bit level in packetC by defining a bitfield container with the bit keyword.

struct MyStruct {
   bits byte {
      flags: 4;
        pad: 4;           // filler, not directly accessible
   } halfUse;
   byte otherHalf;
} myStruct;

Unions allow variables to be accessed in different ways:

union MyBytes {
   int i;
   short s[2];
   byte  b[4];
} myBytes;

Unions

A union is a data type construct that associates multiple kinds of data type with a single storage address. The syntax for describing these union members is identical to the syntax for describing structure fields. However, union members specify different ways of interpreting the bit pattern stored at a single address, rather than specify a series of distinct data items stored at consecutive addresses. The union will be stored in a manner that accommodates its largest member. A union may be followed by unnamed pad bytes.

A packetC descriptor cannot be a union member but it can contain a union as one of its fields. An empty union (one with no members) is not legal in packetC.

// union holds 4 bytes, accessible three different ways
union FourBytes {
    int    i;
    short  s[2];
    byte   b[4];
} fourBytes;

Because type specifiers (e.g., const) are applied only to entire variables, they cannot be applied to an individual union member.

C99 has two ways to initialize a union, namely, (a) constant value will be used to init the 1st member, or (b) use a designator (these are also used for arrays and structures). Since packetC does not have designators, the first member must be used to initialize the union.

A union initialization clause consists of a constant expression that initializes the first member declared in the union. An expression is a legal initializer for the first union member only if it could legally initialize the member as a stand-alone item. Users wishing to explicitly initialize all bits of a union should declare the largest union member first.

union UnionDef { short s; int i;} u = {0xabff};    // value is 0xabff0000
struct StructDef { short src; short dest;};
union UnionDef2 { StructDef astruct; int i;}
UnionDef2 u2 = {{0xabff,0xcdee}};                  // value is 0xabffcdee
<0xabffcdee reflects big endian machine>

Structures

Structures are user-defined aggregate types, which consist of a sequence of fields. Each field is, itself, an object defined by a scalar or an aggregate data type. In packetC, the structure can be accessed as a whole or individual fields can be accessed using a structureName“.”fieldname syntax. packetC provides only naturally aligned structures.

packetC does not support the following structure features that appear in C99:

  • an unsized array as the final field in a structure
  • empty structures (those with no fields)
  • pointers to a structure of the kind being defined

Because type specifiers (e.g., const) are applied only to entire variables, they cannot be applied to an individual structure field.

Structure Alignment

Most modern CPUs have conventions for aligning data according to data type. Data is naturally aligned when its storage begins on a byte address that is a multiple of the datum's size in bytes. Some systems require data to be aligned, while others allow misaligned data but incur a performance penalty for it. If two data types have different alignment requirements, the more demanding requirement is the one that requires that the address be a multiple of a larger number.

The proper alignments for the various data types are as follows:

  • byte:               aligned on any address,
  • short:              aligned on addresses that are multiples of 2,
  • int:                  aligned on addresses that are multiples of 4,
  • long:                aligned on addresses that are multiples of 8,
  • arrays:            aligned on the kind of addresses required by an individual element,
  • structure:        aligned according to the most demanding requirement of its constituent fields.
  • union:              aligned according to the member with the most demanding requirement

packetC provides only naturally aligned structures, thus, it differs significantly from C99 alignment. This property is dictated by the envisioned use of packetC structures to define types that match common network protocols and headers. Such protocols are naturally aligned, which largely avoid internal padding because of the cumulative overhead that padding would create in data communications. A packetC developer must properly pad every defined structure to conform to natural alignment. Developers must be cognizant of this when moving structured data back and forth with array-based representations.

Figure 9-2 shows how C99 and packetC would organize structures with the following definition:

// structure will start on 4 byte-aligned address
// (driven by field i1)
   struct MyStruct {
        byte    b1;
        short   s1;
        byte    b2;
        int     i1;
};
images

Figure 9-2. Structure in C99 and packetC

Types, Tags, and Name Visibility

packetC's treatment of structure and union names or tags, differs significantly from that of C99. First, packetC treats these identifiers as ordinary type names. Thus, the user does not have to qualify the tag with the struct or union keywords.

struct MyStruct {byte b1; short s1;};
MyStruct s1, s2;        // legal in packetC

Second, packetC does not associate tags with a global namespace. Instead, a structure or union type's name is associated with the scope where it is declared, like other identifiers.

Finally, the visibility rules for types declared within structures or unions are as follows:

  • Within the enclosing declaration, a nested declaration is visible from the textual point at which it appears until the end of the enclosing declaration.
  • Following the enclosing declaration's end, the nested declaration is visible anywhere that the outermost enclosing structure/union declaration is visible.
struct Outer {
      byte b1, b2;
      struct Inner {
         short s1;
         int i1;
      } sa;
      Inner sb;
};
Inner si = {16, 32}; // 'Inner' is visible anywhere 'Outer' is visible
Outer so = {8, 7, {16,32}, {16,32}};

Nested structure or union declarations, enumeration type declarations, and typedef alias declarations may appear within a structure or union declaration. However, these may only appear if they declare a structure field or union member for an enclosing declaration, whether it is the outermost declaration or not.

Individual structure fields or union members shall not be initialized within a type declaration nor be declared as const.

Field Selection Operator postfix_expression. identifier

A postfix expression followed by the field selection operator (‘.’) and an identifier constitutes a structure or union field named by the identifier. A field selection operator is only legal when the postfix expression to its left identifies a structure, union, or descriptor.

struct S { int x; } myStruct;
myStruct.x = 15;

Bitfields

Bitfield Declarations
  • Related bit fields are grouped into collections that have a name and are associated with an unambiguous container type (one of the integer types); it is a fatal error if the container is not large enough to accommodate all the combined bit fields.
  • There can be one or more fields in a collection that cannot be directly accessed; these fields have the predefined name, pad.
  • It is a fatal error if the size of the combined bit fields, possibly including one or more pad fields, does not equal the container size.
  • A bit field may not straddle two adjacent bit field collections; each bit field must completely fit into its host container.
  • Bitfields are specified with the same variable“.”fieldname syntax used for other structure fields.
  • Individual bitfields can only be tested and set, although an entire bit field container can be used as an operand by any arithmetic or logical operation that is valid for integers.

The following example shows packetC bit field declaration syntax:

struct IpHeader {
      bits short {
      version   :4;
      headerLen :4;
      tos       :8;
   } first16;                            // collection is named, like any other field
        …
} myHeader;
if ( myHeader.first16.headerLen > 12 ) …

        struct MyStructureDef {
           bits byte {
                flags: 4;
                pad: 4;                  // not directly accessible
           } halfUse;
           byte otherHalf;
        } myStruct;
        myStruct.halfUse.flags = ~0;     // set all flags
        myStruct.halfUse.pad = ~0;       // ERROR: pad fields not accessible
Bitfield Semantics

Bitfields can be used as operands with a restricted set of operators (see below). In order to produce portable results and avoid silent type promotions, bitfield operations center on the role of the bitfield container. The mechanics of bitfield operations are described below.

  • A bitfield can only be used as an operand with the following operators: =, ==, !=, >, <, >=, <=.
  • When used as an operand, an N-bit bitfield acts as if it occupied the least significant N bits of a temporary container in which any other bits are set to zero.
  • When bitfield operands are used in binary operations, all the bits in the temporary container are used, which yields logical results equivalent to C99's.
struct S1 { bits short { a04:4; a12:12;} con1; } sa;
struct S2 { bits long  { b04:4; b60:60;} con2; } sb;
struct S3 { bits short { c02:2; c14:14;} con3; } sc;

sa.con1.a12 = 0xabc;
sa.con1.a04 = 0xd;
if ( sa.con1.a04 > sa.con1.a12 )…            // eval false, a12's hi bits used
  • When a bitfield expression is typecast, the cast affects the size of the bitfield's container, not the size of the bitfield.
  • When a bitfield is used in a binary operation, one of the operands must be cast if the other operand is a scalar with a type different from the bitfield's container or is a bitfield with a different container type.
if ( sa.con1.a04 < (short)sb.con2.b60 )…     // cmp a04 vs. b60 low 16 bits
  • When a bitfield operates as an assignment left-hand-side (LHS):
    • The right-hand-side must be cast if it is a scalar or a bitfield container with a type other than the LHS container.
    • The assignment expression result has the type of the LHS container, even if the LHS bitfield cannot store all of that result.
short sVar = sa.con1.a04 = sc.con3.c14;      // sVar = c14's value
  • When bitfields appear on both sides of an assignment operator, given a left-hand-side bitfield, lbf, with length L1 and a right-hand-side bitfield, rbf, with length L2:
    • If ( L1 <= L2) lbf gets the least significant L1 bits from rbf.
    • If ( L1 > L2) lbf bits 0:L2-1 = rbf and lbf bits L2:L1-1 = 0 (rbf container high bits).

For more information on bitfields, refer to the extended discussion on container based bitfields at the end of this chapter.

A Discussion on Container-Based Bitfields

In packetC, packets are represented as a byte array that reflects its byte arrival order. The outermost header's location can be determined as an offset from the packet's start and using descriptors subsequent headers and individual fields can be identified. However, to effectively process header contents, to alter header content, and to change the protocols being used entirely, we need to read and write individual header fields and entire headers. Hence, we need to be able to manipulate headers with confidence as C-style structures. Since many standard fields are smaller than typical integer storage units (32, 16, 8 bits) or do not take up an integral number of bytes, some form of bit field representation is needed. A packet is assigned to a context to process where the offset specified by a descriptor is specific to the packet being processed by a given instance of packet main as identified by private memories in Figure 9-3.

images

Figure 9-3. Mapping the packet processing model to packetC language constructs

C's bit field construct is not adequate because the implementation freedom it bestows creates a variety of uncertainties, as discussed below.

struct structTag {
        unsigned int notAbitField;
        unsigned char  a:4;
        unsigned int  b:2;
        unsigned int  c:4;
} myStruct;
  • ‘Straddle’ behavior—The entire field named c cannot fit in a byte allocated for a and b. Some compilers let it ‘straddle' bytes, with 2 bits in the byte allocated for the first two fields and the remaining bits in a trailing byte, but others do not. The C99 specification comments: “if insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined.”
  • Container size—Similarly, the compiler may or may not heed the user's specification of the integer storage unit to use for the bit fields. The specification says an implementation can use “any addressable storage unit large enough” to accommodate the bit field.
  • Bit field layout—Finally, we cannot be certain whether the compiler allocates the topmost fields in the declaration to the least significant bytes of the corresponding memory or how the containing unit is aligned: “the order of allocation of bit fields within a unit is implementation-defined. The alignment of the addressable storage unit is undefined.”

In packetC, packet header representation will remain identical on all platforms, no matter the underlying endianness. In particular, it is highly desirable to be able to port an application to new processors or compilers without recoding to reflect new bit field implementation peculiarities. Providing predictable bit field layouts is a key to the packetC approach.

packetC bit field rules produce the following code, using the structure from the previous example (alphabetic superscripts map to the bulleted points that follow):

struct structTag {
     int notAbitField;
     bits short {       (a)
        a:4;            (b)
        b:2;
        c:4;
        pad:6;          (c)
     } containerName;   (d)
} myStruct;

The following comments apply to the footnote annotations shown in the example above:

  1. Related bit fields are explicitly organized inside a container, which has one of packetC's 4 unsigned integer types: byte, short, int, and long.
  2. Since a bit field is always part of a container that has a type, each bit field declares a name and a size, not a type.
  3. Pad fields are declared explicitly. A group of related bit fields, including pad fields, must always sum to the size of their container. Pad fields cannot be accessed for test or set operations.
  4. The container name can be used to access and manipulate the bit field collection as a whole.

This approach removes container size, straddling, and boundary uncertainties by guaranteeing that the storage unit size is the one specified by the user, by forbidding straddling and by ensuring that every bit in the container has been explicitly defined. In addition, we need to manage byte allocation order.

This contrasts with C, in which any data declaration wider than one byte does not use the same byte allocation order when they are compiled and run on big-endian and little-endian processors. User operations on structure fields that correspond to whole integer values do not show effects due to host processor endianness. However, operations on bit fields, which can be sub-elements of integer storage units or can straddle them, do yield endian-specific results.

Big-endian refers to machines that store the most significant bits of a word at the lowest byte address, while little endian machines store the least significant bits starting at the lowest byte address. The example below shows how these practices affect C bit fields.

int bytes4 = 0xabcdef12, *p = &bytes4;
typedef struct sTag {
   unsigned int first : 8;
   unsigned int second: 24;
} sType;
sType myStruct, *pStruct = *((sType*)p);

Big- and little-endian processors store the byte sequences as shown below, where the lower numbered byte addresses appear to the left of higher ones. The big-endian list is shown with big-endian bit allocation order (the most significant half of a byte appears to the left of the least significant one), while the little-endian list shows the least-significant byte to the left.

// Big Endian:      a b | c d | e f | 1 2

// Little Endian:   2 1 | f e | d c | b a

With these storage patterns, a C program will interpret the field values as shown below (where the most significant half-byte value appears to the left of the least significant half):

Big Endian               Little Endian
first = 0xab;            first = 0x12;
second = 0xcdef12        second = 0xabcdef

To provide certainty and portability, packetC imposes a single endianness scheme, the one that best matches its overall processing approach, then uses relatively minor compiler adjustments to compensate when hosted on a little-endian processor. However, the following background discussion shows that the choice is not arbitrary.

Since packet contents appear in network byte order (big-endian order) and since at the bit level protocols most often use little-endian bit-allocation order (e.g., Ethernet), packetC structures and unions are required to be in big-endian order to match the packet's organization and facilitate rapidly reading or writing protocol information between the packet array and user structures. Bit level representation follows little-endian representation in packetC. In practice on traditional C systems, modest code adjustments are required to compensate for bit field access on a little-endian platform. A packetC platform will perform any compensation for the developer to always maintain a predictable environment.

The packetC approach to bit fields ensures that we can match protocol headers that reside in the packet array. Protocol headers are the most common elements represented by complex data types in packetC. Byte- and bit-field representation, and the predictability introduced by packetC is core to network application design. Network applications focus on byte and bit twiddling on packet headers and in packetC, unlike C, this can be done without the need for protective measures to ensure cross-platform portability. Simply by knowing a protocol header's location within the packet, fields can be directly addressed.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.144.248