I talked about two things in my original post.
The first is using the last element in a struct as an zero-element array. In C this idiom is used to handle variable length arrays of data in binary data:
uint8_t buf[4096];
/* read data into buf */
typedef struct {
int element_count;
struct foobar[0];
} *foobars;
...
foobars *foos = (foobars)buf;
for(int i=0; i < foos->element_count; i++) {
.... do something with foos->foobar[i] ...
}
This idiom is very common in protocols. Sometimes there is a lot more than just a length count at the beginning of the struct. Here's a really long example from one of my projects:
typedef struct {
/* encap header */
uint16_t encap_command; /* ALWAYS 0x006f Unconnected Send*/
uint16_t encap_length; /* packet size in bytes - 24 */
uint32_t encap_session_handle; /* from session set up */
uint32_t encap_status; /* always _sent_ as 0 */
uint64_t encap_sender_context; /* whatever we want to set this to, used for
* identifying responses when more than one
* are in flight at once.
*/
uint32_t encap_options; /* 0, reserved for future use */
/* Interface Handle etc. */
uint32_t interface_handle; /* ALWAYS 0 */
uint16_t router_timeout; /* in seconds */
/* Common Packet Format - CPF Unconnected */
uint16_t cpf_item_count; /* ALWAYS 2 */
uint16_t cpf_nai_item_type; /* ALWAYS 0 */
uint16_t cpf_nai_item_length; /* ALWAYS 0 */
uint16_t cpf_udi_item_type; /* ALWAYS 0x00B2 - Unconnected Data Item */
uint16_t cpf_udi_item_length; /* REQ: fill in with length of remaining data. */
/* CM Service Request - Connection Manager */
uint8_t cm_service_code; /* ALWAYS 0x54 Forward Open Request */
uint8_t cm_req_path_size; /* ALWAYS 2, size in words of path, next field */
uint8_t cm_req_path[4]; /* ALWAYS 0x20,0x06,0x24,0x01 for CM, instance 1*/
/* Forward Open Params */
uint8_t secs_per_tick; /* seconds per tick */
uint8_t timeout_ticks; /* timeout = srd_secs_per_tick * src_timeout_ticks */
uint32_t orig_to_targ_conn_id; /* 0, returned by target in reply. */
uint32_t targ_to_orig_conn_id; /* what is _our_ ID for this connection, use ab_connection ptr as id ? */
uint16_t conn_serial_number; /* our connection serial number ?? */
uint16_t orig_vendor_id; /* our unique vendor ID */
uint32_t orig_serial_number; /* our unique serial number */
uint8_t conn_timeout_multiplier;/* timeout = mult * RPI */
uint8_t reserved[3]; /* reserved, set to 0 */
uint32_t orig_to_targ_rpi; /* us to target RPI - Request Packet Interval in microseconds */
uint16_t orig_to_targ_conn_params; /* some sort of identifier of what kind of PLC we are??? */
uint32_t targ_to_orig_rpi; /* target to us RPI, in microseconds */
uint16_t targ_to_orig_conn_params; /* some sort of identifier of what kind of PLC the target is ??? */
uint8_t transport_class; /* ALWAYS 0xA3, server transport, class 3, application trigger */
uint8_t path_size; /* size of connection path in 16-bit words
* connection path from MSG instruction.
*
* EG LGX with 1756-ENBT and CPU in slot 0 would be:
* 0x01 - backplane port of 1756-ENBT
* 0x00 - slot 0 for CPU
* 0x20 - class
* 0x02 - MR Message Router
* 0x24 - instance
* 0x01 - instance #1.
*/
uint8_t conn_path[ZLA_SIZE]; /* connection path as above */
} eip_forward_open_request;
The last field, conn_path, uses a macro ZLA_SIZE that is either blank (MSVC) or 0 (GCC). The second to last field actually contains the length of the connection path. After I read in a packet, I cast to a pointer to this struct and then I can access all the fields, including the connection path, as if the struct was variable size.
The command I had about variable length arrays was not really related to this. Sorry for the confusion.
Best,
Kyle