Close

Code: Printing bitfields

A project log for reprint: modern printf

reprint is printf redone with decades of hindsight, revamping the semantics and syntax to make a function worthy of Hello World.

analog-twoAnalog Two 04/05/2016 at 03:481 Comment

Data that is tightly packed typically ignores 8 bit boundaries, which entails much shifting and masking. reprintf eliminates the need for the programmer to their own shifts and masks. The following code shows printing an IPv4 Header.

/* 
    \fN;ncw prints N bits of data from this pool (in decimal by default).  */
const char test_reprint_ipv4[] = 
    "\f\r0=cq"                     // Specify packed data
                                   // and load 16 bits; no printing
    "Version:           \f4;ncw\n" // Print  4 bits;  4 total
    "Header Words:      \f4;ncw\n" // Print  4 bits;  8 total
    "DSCP:              \f6;ncw\n" // Print  6 bits; 14 total
    "ECN:               \f2;cw\n"  // Print  2 bits; 16 total
    "Total Bytes:       \fcnq\n"   // Print 16 bit value
    "Identification:    \fcnq\n"   // Print 16 bit value
    "\f0=cq"                       // Load  16 bits; no printing
    "Flags:             \f&3;ncw\n" // Print  3 bits in binary;  3 total
    "Fragment Offset:   \f13;cw\n"  // Print 13 bits;           16 total
    "Protocol:          \fcp\n"    // Print  8 bit value
    "TTL:               \fcp\n"    // Print  8 bit value
    "Header Checksum:   \fcnq\n"   // Print 16 bit value
    "Source IP:         \fcp.\fcp.\fcp.\fcp\n"  // Print 4 1 byte values
    "Dest IP:           \fcp.\fcp.\fcp.\fcp\n"; // Print 4 1 byte values

reprintf_ptr(test_reprint_ipv4, incoming_packet);

The packing directive "\r" indicates the data format is tightly packed, rather than struct packed.

The input specifier "cq" corresponds to "uint16_t", so exactly 16 bits are loaded into the Value register. The "0=" sequence specifically loads 0 into Register 4, which for formatted integer output governs the number of significant digits printed. Printing 0 significant digits is essentially a no-op but leaves the value loaded in the Value register. The input modifier "n" indicates the input datum is big endian formatted.

The input specifier "cw" specifically calls out bitfields and assumes the bit data was already loaded into the Value Register. The ';' character identifies Register 3, which is the parameter governing how many bits are output.

Using printf, the equivalent code (without the binary flag output of course) is:

/* Using printf. The format string may appear simpler, but correctly extracting
the data from the packet just to put it on the stack is a painful task.
I'm not even sure if that part is right... */
const char test_printf_ipv4[] = 
    "Version:           %u\n"
    "Header Words:      %u\n"
    "DSCP:              %u\n"
    "ECN:               %u\n"
    "Total Bytes:       %u\n"
    "Identification:    %u\n"
    "Flags:             %x\n"
    "Fragment Offset:   %u\n"
    "Protocol:          %u\n"
    "TTL:               %u\n"
    "Header Checksum:   %u\n"
    "Source IP:         %u.%u.%u.%u\n"
    "Dest IP:           %u.%u.%u.%u\n";

printf(test_printf_ipv4
    ,incoming_packet[0] >> 4
    ,incoming_packet[0] & 0xF
    ,incoming_packet[1] >> 2
    ,incoming_packet[1] & 0x3
    ,*(uint16_t*)(incoming_packet + 2)
    ,*(uint16_t*)(incoming_packet + 4)
    ,incoming_packet[5] >> 5
    ,*(uint16_t*)(incoming_packet + 6) & 0x1FFF
    ,incoming_packet[8]
    ,incoming_packet[9]
    ,*(uint16_t*)(incoming_packet + 10)
    ,incoming_packet[12]
    ,incoming_packet[13]
    ,incoming_packet[14]
    ,incoming_packet[15]
    ,incoming_packet[16]
    ,incoming_packet[17]
    ,incoming_packet[18]
    ,incoming_packet[19]);

Discussions

Eric Hertz wrote 04/05/2016 at 13:16 point

nice!

  Are you sure? yes | no