I found an answer that worked for me. I simply loop within the dissector, incrementing an offset
value with each field I process until offset > tvb_reported_length(tvb)
. In each loop I add a new subtree to the parent tree item, so that each individual message within the frame is in its own tree.
EDIT: For your amusement, here's the complete disscection code for CQS, including the code that handles multiple packets per UDP frame:
static int dissect_cqs(tvbuff_t* tvb,
packet_info* pinfo,
proto_tree* tree)
{
guint offset = 0;
proto_item* ti = 0;
char msgcat = '\0';
gint field_sz;
static const char soh = 0x01, us = 0x1F, etx = 0x03;
proto_tree* cqs_tree, *frame_tree, *packet_tree;
guint8 ts_hour, ts_minute,ts_second;
char ts_msec_str[3];
char cattype [2];
int found = 0;
guint i;
char seq_str[9+1];
guint32 seq;
guint32 firstseq = 0, msgcount = 0;
guint framegaps = 0;
col_set_str(pinfo->cinfo, COL_PROTOCOL, "CQS");
col_clear(pinfo->cinfo, COL_INFO);
ti = proto_tree_add_item(tree, proto_cqs, tvb, offset, -1, FALSE);
cqs_tree = proto_item_add_subtree(ti, ett_cqs);
ti = proto_tree_add_item(cqs_tree, hf_cqs_frame_subtree, tvb, offset, -1, FALSE);
frame_tree = proto_item_add_subtree(ti, ett_frame);
/** APPEND THE LEADING CONTROL CHARACTER **/
field_sz = 1; ti = proto_tree_add_item(frame_tree, hf_cqs_ctrl, tvb, offset, field_sz, FALSE) ; offset += field_sz;
while( offset < tvb_reported_length(tvb) )
{
/** get the message type, category & sequence number **/
tvb_memcpy(tvb, cattype, offset, 2);
tvb_memcpy(tvb, seq_str, offset+8, 9);
seq_str[9] = 0;
seq = atol(seq_str);
++msgcount;
if( !firstseq )
firstseq = seq;
else
framegaps += (seq - firstseq) - (msgcount - 1);
ti = proto_tree_add_none_format(frame_tree, hf_cqs_packet_subtree, tvb, offset, 24, "CQS Packet [%ld]", seq);
if( ti ) packet_tree = proto_item_add_subtree(ti, ett_packet);
else packet_tree = 0;
/** dissect the packet header **/
cattype[0] = tvb_get_guint8(tvb, offset);
field_sz = 1; ti = proto_tree_add_item(packet_tree, hf_cqs_hdr_msgcat, tvb, offset, field_sz, FALSE) ; offset += field_sz;
cattype[1] = tvb_get_guint8(tvb, offset);
field_sz = 1; ti = proto_tree_add_item(packet_tree, hf_cqs_hdr_msgtype, tvb, offset, field_sz, FALSE) ; offset += field_sz;
field_sz = 1; ti = proto_tree_add_item(packet_tree, hf_cqs_hdr_msgnet, tvb, offset, field_sz, FALSE) ; offset += field_sz;
field_sz = 2; ti = proto_tree_add_item(packet_tree, hf_cqs_hdr_retran, tvb, offset, field_sz, FALSE) ; offset += field_sz;
field_sz = 1; ti = proto_tree_add_item(packet_tree, hf_cqs_hdr_hdrid, tvb, offset, field_sz, FALSE) ; offset += field_sz;
field_sz = 2; ti = proto_tree_add_item(packet_tree, hf_cqs_hdr_rsv, tvb, offset, field_sz, FALSE) ; offset += field_sz;
field_sz = 9; ti = proto_tree_add_item(packet_tree, hf_cqs_hdr_seq, tvb, offset, field_sz, FALSE) ; offset += field_sz;
field_sz = 1; ti = proto_tree_add_item(packet_tree, hf_cqs_hdr_partid, tvb, offset, field_sz, FALSE) ; offset += field_sz;
ts_hour = tvb_get_guint8(tvb, offset) - '0';
ts_minute = tvb_get_guint8(tvb, offset+1) - '0';
ts_second = tvb_get_guint8(tvb, offset+2) - '0';
ts_msec_str[0] = tvb_get_guint8(tvb, offset+3);
ts_msec_str[1] = tvb_get_guint8(tvb, offset+4);
ts_msec_str[2] = tvb_get_guint8(tvb, offset+5);
field_sz = 6; ti = proto_tree_add_item(packet_tree, hf_cqs_hdr_time, tvb, offset, field_sz, FALSE) ; offset += field_sz;
if( tree )
{
proto_item_set_text(ti, "CQS Timestamp: %02d:%02d:%02d.%c%c%c %cM", (ts_hour > 11) ? (ts_hour-12) : ts_hour, ts_minute, ts_second, ts_msec_str[0], ts_msec_str[1], ts_msec_str[2], (ts_hour > 11) ? 'P' : 'A');
}
/** COUNT UP BYTES UNTIL WE FIND A CONTROL CHARACTER **/
field_sz = 0;
found = 0;
for( i = 0; !found && offset+i < tvb_reported_length(tvb); ++i )
{
char c = tvb_get_guint8(tvb, offset+i);
switch( c )
{
case 0x01 : /** SOH **/
case 0x1F : /** US **/
case 0x03 : /* ETX **/
found = 1;
break;
default :
++field_sz;
break;
}
}
/** APPEND THE DATA PAYLOAD **/
ti = proto_tree_add_item(packet_tree, hf_cqs_payload, tvb, offset, field_sz, FALSE) ; offset += field_sz;
/** APPEND THE TRAILING CONTROL CHARACTER **/
field_sz = 1; ti = proto_tree_add_item(frame_tree, hf_cqs_ctrl, tvb, offset, field_sz, FALSE) ; offset += field_sz;
}
ti = proto_tree_add_uint(cqs_tree, hf_cqs_frame_first_seq, tvb, 0, 0, firstseq);
ti = proto_tree_add_uint(cqs_tree, hf_cqs_frame_expected_seq, tvb, 0, 0, firstseq+msgcount);
ti = proto_tree_add_uint(cqs_tree, hf_cqs_frame_msgcount, tvb, 0, 0, msgcount);
ti = proto_tree_add_uint(cqs_tree, hf_cqs_frame_gaps, tvb, 0, 0, framegaps);
return tvb_length(tvb);
}