forked from osmocom/wireshark
![Dario Lombardo](/assets/img/avatar_default.png)
Change-Id: I92c94448e6641716d03158a5f332c8b53709423a Reviewed-on: https://code.wireshark.org/review/25756 Petri-Dish: Dario Lombardo <lomato@gmail.com> Reviewed-by: Anders Broman <a.broman58@gmail.com>
3121 lines
135 KiB
C
3121 lines
135 KiB
C
/* packet-aeron.c
|
|
*
|
|
* Wireshark - Network traffic analyzer
|
|
* By Gerald Combs <gerald@wireshark.org>
|
|
* Copyright 1998 Gerald Combs
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <epan/packet.h>
|
|
#include <epan/prefs.h>
|
|
#include <epan/expert.h>
|
|
#include <epan/uat.h>
|
|
#include <epan/tap.h>
|
|
#include <epan/conversation.h>
|
|
#include <epan/exceptions.h>
|
|
#include <epan/to_str.h>
|
|
#include <wsutil/pint.h>
|
|
|
|
/* The Aeron protocol is defined at https://github.com/real-logic/Aeron/wiki/Protocol-Specification */
|
|
|
|
void proto_register_aeron(void);
|
|
void proto_reg_handoff_aeron(void);
|
|
|
|
/* Protocol handle */
|
|
static int proto_aeron = -1;
|
|
|
|
/* Dissector handles */
|
|
static dissector_handle_t aeron_dissector_handle;
|
|
static heur_dissector_list_t aeron_heuristic_subdissector_list;
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Preferences. */
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
static gboolean aeron_sequence_analysis = FALSE;
|
|
static gboolean aeron_stream_analysis = FALSE;
|
|
static gboolean aeron_reassemble_fragments = FALSE;
|
|
static gboolean aeron_use_heuristic_subdissectors = FALSE;
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Aeron position routines. */
|
|
/*----------------------------------------------------------------------------*/
|
|
typedef struct
|
|
{
|
|
guint32 term_id;
|
|
guint32 term_offset;
|
|
} aeron_pos_t;
|
|
|
|
static int aeron_pos_roundup(int offset)
|
|
{
|
|
return ((offset+7) & 0xfffffff8);
|
|
}
|
|
|
|
static int aeron_pos_compare(const aeron_pos_t * pos1, const aeron_pos_t * pos2)
|
|
{
|
|
/* Returns:
|
|
< 0 if pos1 < pos2
|
|
== 0 if pos1 == pos2
|
|
> 0 if pos1 > pos2
|
|
*/
|
|
if (pos1->term_id == pos2->term_id)
|
|
{
|
|
if (pos1->term_offset == pos2->term_offset)
|
|
{
|
|
return (0);
|
|
}
|
|
else
|
|
{
|
|
return ((pos1->term_offset < pos2->term_offset) ? -1 : 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return ((pos1->term_id < pos2->term_id) ? -1 : 1);
|
|
}
|
|
}
|
|
|
|
static guint32 aeron_pos_delta(const aeron_pos_t * pos1, const aeron_pos_t * pos2, guint32 term_size)
|
|
{
|
|
const aeron_pos_t * p1;
|
|
const aeron_pos_t * p2;
|
|
guint64 p1_val;
|
|
guint64 p2_val;
|
|
guint64 delta;
|
|
int rc;
|
|
|
|
rc = aeron_pos_compare(pos1, pos2);
|
|
if (rc >= 0)
|
|
{
|
|
p1 = pos1;
|
|
p2 = pos2;
|
|
}
|
|
else
|
|
{
|
|
p1 = pos2;
|
|
p2 = pos1;
|
|
}
|
|
p1_val = ((guint64) p1->term_id * term_size) + ((guint64) p1->term_offset);
|
|
p2_val = ((guint64) p2->term_id * term_size) + ((guint64) p2->term_offset);
|
|
delta = p1_val - p2_val;
|
|
return ((guint32) (delta & G_GUINT64_CONSTANT(0x00000000ffffffff)));
|
|
}
|
|
|
|
static gboolean aeron_pos_add_length(aeron_pos_t * pos, guint32 length, guint32 term_length)
|
|
{
|
|
guint32 next_term_offset;
|
|
guint32 rounded_next_term_offset;
|
|
|
|
next_term_offset = pos->term_offset + length;
|
|
if (next_term_offset < pos->term_offset)
|
|
return FALSE; /* overflow */
|
|
rounded_next_term_offset = aeron_pos_roundup(next_term_offset);
|
|
if (rounded_next_term_offset < next_term_offset)
|
|
return FALSE; /* overflow */
|
|
next_term_offset = rounded_next_term_offset;
|
|
|
|
if (next_term_offset >= term_length)
|
|
{
|
|
pos->term_offset = 0;
|
|
pos->term_id++;
|
|
}
|
|
else
|
|
{
|
|
pos->term_offset = next_term_offset;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Aeron frame information management. */
|
|
/*----------------------------------------------------------------------------*/
|
|
static wmem_tree_t * aeron_frame_info_tree = NULL;
|
|
|
|
struct aeron_frame_info_t_stct;
|
|
typedef struct aeron_frame_info_t_stct aeron_frame_info_t;
|
|
|
|
typedef struct
|
|
{
|
|
aeron_frame_info_t * frame_info; /* Frame (aeron_frame_info_t) containing the RX data */
|
|
guint32 term_offset; /* Term offset of RX data */
|
|
guint32 length; /* Length of RX data */
|
|
} aeron_rx_info_t;
|
|
|
|
typedef struct
|
|
{
|
|
aeron_frame_info_t * frame_info; /* Frame (aeron_frame_info_t) in which this NAK occurs */
|
|
wmem_list_t * rx; /* List of RX frames for this NAK */
|
|
guint32 flags;
|
|
guint32 nak_term_offset; /* Term offset specified by this NAK */
|
|
guint32 nak_length; /* NAK length */
|
|
guint32 unrecovered_length; /* Number of bytes unrecovered via RX */
|
|
} aeron_nak_analysis_t;
|
|
|
|
typedef struct
|
|
{
|
|
guint32 flags;
|
|
guint32 flags2;
|
|
aeron_pos_t high;
|
|
aeron_pos_t completed;
|
|
guint32 receiver_window;
|
|
guint32 outstanding_bytes;
|
|
} aeron_stream_analysis_t;
|
|
#define AERON_STREAM_ANALYSIS_FLAGS_WINDOW_FULL 0x00000001
|
|
#define AERON_STREAM_ANALYSIS_FLAGS_IDLE_RX 0x00000002
|
|
#define AERON_STREAM_ANALYSIS_FLAGS_PACING_RX 0x00000004
|
|
#define AERON_STREAM_ANALYSIS_FLAGS_OOO 0x00000008
|
|
#define AERON_STREAM_ANALYSIS_FLAGS_OOO_GAP 0x00000010
|
|
#define AERON_STREAM_ANALYSIS_FLAGS_KEEPALIVE 0x00000020
|
|
#define AERON_STREAM_ANALYSIS_FLAGS_WINDOW_RESIZE 0x00000040
|
|
#define AERON_STREAM_ANALYSIS_FLAGS_OOO_SM 0x00000080
|
|
#define AERON_STREAM_ANALYSIS_FLAGS_KEEPALIVE_SM 0x00000100
|
|
#define AERON_STREAM_ANALYSIS_FLAGS_RX 0x00000200
|
|
#define AERON_STREAM_ANALYSIS_FLAGS_TERM_ID_CHANGE 0x00000400
|
|
|
|
#define AERON_STREAM_ANALYSIS_FLAGS2_RCV_VALID 0x00000001
|
|
|
|
typedef struct
|
|
{
|
|
guint32 previous;
|
|
guint32 next;
|
|
} aeron_frame_link_t;
|
|
|
|
struct aeron_msg_t_stct;
|
|
typedef struct aeron_msg_t_stct aeron_msg_t;
|
|
|
|
struct aeron_frame_info_t_stct
|
|
{
|
|
guint32 frame;
|
|
guint32 ofs;
|
|
aeron_frame_link_t transport;
|
|
aeron_frame_link_t stream;
|
|
aeron_frame_link_t term;
|
|
aeron_frame_link_t fragment;
|
|
aeron_stream_analysis_t * stream_analysis;
|
|
aeron_nak_analysis_t * nak_analysis;
|
|
aeron_msg_t * message;
|
|
wmem_list_t * rx;
|
|
guint32 flags;
|
|
};
|
|
#define AERON_FRAME_INFO_FLAGS_RETRANSMISSION 0x00000001
|
|
#define AERON_FRAME_INFO_FLAGS_KEEPALIVE 0x00000002
|
|
#define AERON_FRAME_INFO_FLAGS_REASSEMBLED_MSG 0x00000004
|
|
|
|
static wmem_tree_key_t * aeron_frame_info_key_build(guint32 frame, guint32 ofs)
|
|
{
|
|
wmem_tree_key_t * fkey;
|
|
guint32 * key;
|
|
|
|
fkey = wmem_alloc_array(wmem_packet_scope(), wmem_tree_key_t, 2);
|
|
key = wmem_alloc_array(wmem_packet_scope(), guint32, 2);
|
|
key[0] = frame;
|
|
key[1] = ofs;
|
|
fkey[0].length = 2;
|
|
fkey[0].key = key;
|
|
fkey[1].length = 0;
|
|
fkey[1].key = NULL;
|
|
return (fkey);
|
|
}
|
|
|
|
static aeron_frame_info_t * aeron_frame_info_lookup(wmem_tree_key_t * key)
|
|
{
|
|
aeron_frame_info_t * fi;
|
|
|
|
fi = (aeron_frame_info_t *) wmem_tree_lookup32_array(aeron_frame_info_tree, key);
|
|
return (fi);
|
|
}
|
|
|
|
static aeron_frame_info_t * aeron_frame_info_find(guint32 frame, guint32 ofs)
|
|
{
|
|
wmem_tree_key_t * key = aeron_frame_info_key_build(frame, ofs);
|
|
return (aeron_frame_info_lookup(key));
|
|
}
|
|
|
|
static aeron_frame_info_t * aeron_frame_info_add(guint32 frame, guint32 ofs)
|
|
{
|
|
aeron_frame_info_t * fi;
|
|
wmem_tree_key_t * key = aeron_frame_info_key_build(frame, ofs);
|
|
|
|
fi = aeron_frame_info_lookup(key);
|
|
if (fi == NULL)
|
|
{
|
|
fi = wmem_new0(wmem_file_scope(), aeron_frame_info_t);
|
|
fi->frame = frame;
|
|
fi->ofs = ofs;
|
|
if (aeron_sequence_analysis && aeron_stream_analysis)
|
|
{
|
|
fi->rx = wmem_list_new(wmem_file_scope());
|
|
}
|
|
wmem_tree_insert32_array(aeron_frame_info_tree, key, (void *) fi);
|
|
}
|
|
return (fi);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Aeron channel ID management. */
|
|
/*----------------------------------------------------------------------------*/
|
|
static guint64 aeron_channel_id = 1;
|
|
|
|
static guint64 aeron_channel_id_assign(void)
|
|
{
|
|
return (aeron_channel_id++);
|
|
}
|
|
|
|
static void aeron_channel_id_init(void)
|
|
{
|
|
aeron_channel_id = 1;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Aeron transport, stream, term, and fragment structures. */
|
|
/*----------------------------------------------------------------------------*/
|
|
typedef struct
|
|
{
|
|
address * addr1;
|
|
address * addr2;
|
|
guint16 port1;
|
|
guint16 port2;
|
|
} aeron_conversation_info_t;
|
|
|
|
struct aeron_transport_t_stct;
|
|
typedef struct aeron_transport_t_stct aeron_transport_t;
|
|
|
|
struct aeron_stream_t_stct;
|
|
typedef struct aeron_stream_t_stct aeron_stream_t;
|
|
|
|
struct aeron_term_t_stct;
|
|
typedef struct aeron_term_t_stct aeron_term_t;
|
|
|
|
struct aeron_fragment_t_stct;
|
|
typedef struct aeron_fragment_t_stct aeron_fragment_t;
|
|
|
|
struct aeron_transport_t_stct
|
|
{
|
|
guint64 channel_id;
|
|
wmem_map_t * stream; /* Map of all streams (aeron_stream_t) in this transport, keyed by stream ID */
|
|
aeron_frame_info_t * last_frame;
|
|
address addr1;
|
|
address addr2;
|
|
guint32 session_id;
|
|
guint16 port1;
|
|
guint16 port2;
|
|
};
|
|
|
|
struct aeron_stream_rcv_t_stct;
|
|
typedef struct aeron_stream_rcv_t_stct aeron_stream_rcv_t;
|
|
|
|
struct aeron_stream_rcv_t_stct
|
|
{
|
|
address addr; /* Receiver's IP address */
|
|
guint16 port; /* Receiver's (sending) port */
|
|
aeron_pos_t completed;
|
|
guint32 receiver_window;
|
|
};
|
|
|
|
struct aeron_stream_t_stct
|
|
{
|
|
aeron_transport_t * transport; /* Parent transport */
|
|
wmem_map_t * term; /* Map of all terms (aeron_term_t) in this stream, keyed by term ID */
|
|
wmem_list_t * rcv; /* List of receivers (aeron_stream_rcv_t) */
|
|
guint32 rcv_count;
|
|
aeron_frame_info_t * last_frame;
|
|
guint32 stream_id;
|
|
guint32 term_length;
|
|
guint32 mtu;
|
|
guint32 flags;
|
|
aeron_pos_t high;
|
|
};
|
|
#define AERON_STREAM_FLAGS_HIGH_VALID 0x1
|
|
|
|
typedef struct
|
|
{
|
|
aeron_term_t * term; /* Parent term */
|
|
aeron_frame_info_t * frame_info; /* Frame info (aeron_frame_info_t) in which this NAK occurred */
|
|
guint32 term_offset; /* NAK term offset */
|
|
guint32 length; /* Length of NAK */
|
|
} aeron_nak_t;
|
|
|
|
struct aeron_term_t_stct
|
|
{
|
|
aeron_stream_t * stream; /* Parent stream */
|
|
wmem_map_t * fragment; /* Map of all fragments (aeron_fragment_t) in this term, keyed by term offset */
|
|
wmem_tree_t * message; /* Tree of all fragmented messages (aeron_msg_t) in this term, keyed by lowest term offset */
|
|
wmem_list_t * orphan_fragment;
|
|
aeron_frame_info_t * last_frame; /* Pointer to last frame seen for this term */
|
|
wmem_list_t * nak; /* List of all NAKs (aeron_nak_t) in this term */
|
|
guint32 term_id;
|
|
};
|
|
|
|
struct aeron_fragment_t_stct
|
|
{
|
|
aeron_term_t * term; /* Parent term */
|
|
wmem_list_t * frame; /* List of frames (aeron_frame_info_t) containing this fragment (term offset) */
|
|
aeron_frame_info_t * first_frame; /* First frame which contains this fragment (term offset) */
|
|
aeron_frame_info_t * last_frame; /* Last frame which contains this fragment (term offset) */
|
|
aeron_frame_info_t * first_data_frame; /* First frame which contains this fragment (term offset) as actual data (not as a KA) */
|
|
guint32 term_offset;
|
|
guint32 length;
|
|
guint32 data_length;
|
|
guint32 frame_count;
|
|
};
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Aeron transport management. */
|
|
/*----------------------------------------------------------------------------*/
|
|
static guint aeron_guint32_hash_func(gconstpointer key)
|
|
{
|
|
guint32 value = *((const guint32 *) key);
|
|
return ((guint) value);
|
|
}
|
|
|
|
static gboolean aeron_guint32_compare_func(gconstpointer lhs, gconstpointer rhs)
|
|
{
|
|
guint32 key1 = *((const guint32 *) lhs);
|
|
guint32 key2 = *((const guint32 *) rhs);
|
|
return ((key1 == key2) ? TRUE : FALSE);
|
|
}
|
|
|
|
static aeron_transport_t * aeron_transport_add(const aeron_conversation_info_t * cinfo, guint32 session_id, guint32 frame)
|
|
{
|
|
aeron_transport_t * transport;
|
|
conversation_t * conv;
|
|
wmem_map_t * session_map;
|
|
|
|
conv = find_conversation(frame, cinfo->addr1, cinfo->addr2, ENDPOINT_UDP, cinfo->port1, cinfo->port2, 0);
|
|
if (conv == NULL)
|
|
{
|
|
conv = conversation_new(frame, cinfo->addr1, cinfo->addr2, ENDPOINT_UDP, cinfo->port1, cinfo->port2, 0);
|
|
}
|
|
if (frame > conv->last_frame)
|
|
{
|
|
conv->last_frame = frame;
|
|
}
|
|
session_map = (wmem_map_t *) conversation_get_proto_data(conv, proto_aeron);
|
|
if (session_map == NULL)
|
|
{
|
|
session_map = wmem_map_new(wmem_file_scope(), aeron_guint32_hash_func, aeron_guint32_compare_func);
|
|
conversation_add_proto_data(conv, proto_aeron, (void *) session_map);
|
|
}
|
|
transport = (aeron_transport_t *) wmem_map_lookup(session_map, (const void *) &session_id);
|
|
if (transport != NULL)
|
|
{
|
|
return (transport);
|
|
}
|
|
transport = wmem_new0(wmem_file_scope(), aeron_transport_t);
|
|
transport->channel_id = aeron_channel_id_assign();
|
|
transport->stream = wmem_map_new(wmem_file_scope(), aeron_guint32_hash_func, aeron_guint32_compare_func);
|
|
transport->last_frame = NULL;
|
|
copy_address_wmem(wmem_file_scope(), &(transport->addr1), cinfo->addr1);
|
|
copy_address_wmem(wmem_file_scope(), &(transport->addr2), cinfo->addr2);
|
|
transport->session_id = session_id;
|
|
transport->port1 = cinfo->port1;
|
|
transport->port2 = cinfo->port2;
|
|
wmem_map_insert(session_map, (const void *) &(transport->session_id), (void *) transport);
|
|
return (transport);
|
|
}
|
|
|
|
static aeron_stream_t * aeron_transport_stream_find(aeron_transport_t * transport, guint32 stream_id)
|
|
{
|
|
aeron_stream_t * stream;
|
|
|
|
stream = (aeron_stream_t *) wmem_map_lookup(transport->stream, (const void *) &stream_id);
|
|
return (stream);
|
|
}
|
|
|
|
static aeron_stream_t * aeron_transport_stream_add(aeron_transport_t * transport, guint32 stream_id)
|
|
{
|
|
aeron_stream_t * stream;
|
|
|
|
stream = aeron_transport_stream_find(transport, stream_id);
|
|
if (stream == NULL)
|
|
{
|
|
stream = wmem_new0(wmem_file_scope(), aeron_stream_t);
|
|
stream->transport = transport;
|
|
stream->term = wmem_map_new(wmem_file_scope(), aeron_guint32_hash_func, aeron_guint32_compare_func);
|
|
stream->rcv = wmem_list_new(wmem_file_scope());
|
|
stream->rcv_count = 0;
|
|
stream->last_frame = NULL;
|
|
stream->stream_id = stream_id;
|
|
stream->term_length = 0;
|
|
stream->mtu = 0;
|
|
stream->flags = 0;
|
|
stream->high.term_id = 0;
|
|
stream->high.term_offset = 0;
|
|
wmem_map_insert(transport->stream, (const void *) &(stream->stream_id), (void *) stream);
|
|
}
|
|
return (stream);
|
|
}
|
|
|
|
static void aeron_transport_frame_add(aeron_transport_t * transport, aeron_frame_info_t * finfo, guint32 flags)
|
|
{
|
|
if (flags != 0)
|
|
{
|
|
finfo->flags = flags;
|
|
}
|
|
if (transport->last_frame != NULL)
|
|
{
|
|
finfo->transport.previous = transport->last_frame->frame;
|
|
transport->last_frame->transport.next = finfo->frame;
|
|
}
|
|
finfo->transport.next = 0;
|
|
transport->last_frame = finfo;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Aeron stream management. */
|
|
/*----------------------------------------------------------------------------*/
|
|
static aeron_term_t * aeron_stream_term_find(aeron_stream_t * stream, guint32 term_id)
|
|
{
|
|
aeron_term_t * term;
|
|
|
|
term = (aeron_term_t *) wmem_map_lookup(stream->term, (const void *) &term_id);
|
|
return (term);
|
|
}
|
|
|
|
static aeron_term_t * aeron_stream_term_add(aeron_stream_t * stream, guint32 term_id)
|
|
{
|
|
aeron_term_t * term;
|
|
|
|
term = aeron_stream_term_find(stream, term_id);
|
|
if (term == NULL)
|
|
{
|
|
term = wmem_new0(wmem_file_scope(), aeron_term_t);
|
|
term->stream = stream;
|
|
term->fragment = wmem_map_new(wmem_file_scope(), aeron_guint32_hash_func, aeron_guint32_compare_func);
|
|
term->message = wmem_tree_new(wmem_file_scope());
|
|
term->orphan_fragment = wmem_list_new(wmem_file_scope());
|
|
term->nak = wmem_list_new(wmem_file_scope());
|
|
term->term_id = term_id;
|
|
wmem_map_insert(stream->term, (const void *) &(term->term_id), (void *) term);
|
|
}
|
|
return (term);
|
|
}
|
|
|
|
static aeron_stream_rcv_t * aeron_stream_rcv_find(aeron_stream_t * stream, const address * addr, guint16 port)
|
|
{
|
|
wmem_list_frame_t * lf = wmem_list_head(stream->rcv);
|
|
aeron_stream_rcv_t * rcv = NULL;
|
|
|
|
while (lf != NULL)
|
|
{
|
|
aeron_stream_rcv_t * cur = (aeron_stream_rcv_t *) wmem_list_frame_data(lf);
|
|
if (cur != NULL)
|
|
{
|
|
if ((cmp_address(&(cur->addr), addr) == 0) && (cur->port == port))
|
|
{
|
|
rcv = cur;
|
|
break;
|
|
}
|
|
}
|
|
lf = wmem_list_frame_next(lf);
|
|
}
|
|
return (rcv);
|
|
}
|
|
|
|
static aeron_stream_rcv_t * aeron_stream_rcv_add(aeron_stream_t * stream, const address * addr, guint16 port)
|
|
{
|
|
aeron_stream_rcv_t * rcv;
|
|
|
|
rcv = aeron_stream_rcv_find(stream, addr, port);
|
|
if (rcv != NULL)
|
|
{
|
|
return (rcv);
|
|
}
|
|
rcv = wmem_new0(wmem_file_scope(), aeron_stream_rcv_t);
|
|
copy_address_wmem(wmem_file_scope(), &(rcv->addr), addr);
|
|
rcv->port = port;
|
|
rcv->completed.term_id = 0;
|
|
rcv->completed.term_offset = 0;
|
|
rcv->receiver_window = 0;
|
|
wmem_list_append(stream->rcv, (void *) rcv);
|
|
stream->rcv_count++;
|
|
return (rcv);
|
|
}
|
|
|
|
static void aeron_stream_frame_add(aeron_stream_t * stream, aeron_frame_info_t * finfo, guint32 flags)
|
|
{
|
|
if (flags != 0)
|
|
{
|
|
finfo->flags = flags;
|
|
}
|
|
if (stream->last_frame != NULL)
|
|
{
|
|
finfo->stream.previous = stream->last_frame->frame;
|
|
stream->last_frame->stream.next = finfo->frame;
|
|
}
|
|
finfo->stream.next = 0;
|
|
stream->last_frame = finfo;
|
|
aeron_transport_frame_add(stream->transport, finfo, 0);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Aeron term management. */
|
|
/*----------------------------------------------------------------------------*/
|
|
static aeron_fragment_t * aeron_term_fragment_find(aeron_term_t * term, guint32 term_offset)
|
|
{
|
|
aeron_fragment_t * fragment;
|
|
|
|
fragment = (aeron_fragment_t *) wmem_map_lookup(term->fragment, (const void *) &term_offset);
|
|
return (fragment);
|
|
}
|
|
|
|
static aeron_fragment_t * aeron_term_fragment_add(aeron_term_t * term, guint32 term_offset, guint32 length, guint32 data_length)
|
|
{
|
|
aeron_fragment_t * fragment;
|
|
|
|
fragment = aeron_term_fragment_find(term, term_offset);
|
|
if (fragment == NULL)
|
|
{
|
|
fragment = wmem_new0(wmem_file_scope(), aeron_fragment_t);
|
|
fragment->term = term;
|
|
fragment->frame = wmem_list_new(wmem_file_scope());
|
|
fragment->first_frame = NULL;
|
|
fragment->last_frame = NULL;
|
|
fragment->first_data_frame = NULL;
|
|
fragment->term_offset = term_offset;
|
|
fragment->length = length;
|
|
fragment->data_length = data_length;
|
|
fragment->frame_count = 0;
|
|
wmem_map_insert(term->fragment, (const void *) &(fragment->term_offset), (void *) fragment);
|
|
}
|
|
return (fragment);
|
|
}
|
|
|
|
static void aeron_term_frame_add(aeron_term_t * term, aeron_frame_info_t * finfo, guint32 flags)
|
|
{
|
|
if (flags != 0)
|
|
{
|
|
finfo->flags = flags;
|
|
}
|
|
if (term->last_frame != NULL)
|
|
{
|
|
finfo->term.previous = term->last_frame->frame;
|
|
term->last_frame->term.next = finfo->frame;
|
|
}
|
|
finfo->term.next = 0;
|
|
term->last_frame = finfo;
|
|
aeron_stream_frame_add(term->stream, finfo, 0);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Aeron fragment management. */
|
|
/*----------------------------------------------------------------------------*/
|
|
static void aeron_fragment_frame_add(aeron_fragment_t * fragment, aeron_frame_info_t * finfo, guint32 flags, guint32 length)
|
|
{
|
|
if (flags != 0)
|
|
{
|
|
finfo->flags = flags;
|
|
}
|
|
wmem_list_append(fragment->frame, (void *) finfo);
|
|
fragment->frame_count++;
|
|
if (fragment->last_frame != NULL)
|
|
{
|
|
finfo->fragment.previous = fragment->last_frame->frame;
|
|
fragment->last_frame->fragment.next = finfo->frame;
|
|
}
|
|
if (fragment->first_frame == NULL)
|
|
{
|
|
fragment->first_frame = finfo;
|
|
}
|
|
if (length != 0)
|
|
{
|
|
if (fragment->first_data_frame == NULL)
|
|
{
|
|
fragment->first_data_frame = finfo;
|
|
}
|
|
}
|
|
finfo->fragment.next = 0;
|
|
fragment->last_frame = finfo;
|
|
aeron_term_frame_add(fragment->term, finfo, 0);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Utility functions. */
|
|
/*----------------------------------------------------------------------------*/
|
|
static gboolean aeron_is_address_multicast(const address * addr)
|
|
{
|
|
const guint8 * addr_data = (const guint8 *) addr->data;
|
|
|
|
switch (addr->type)
|
|
{
|
|
case AT_IPv4:
|
|
if ((addr_data[0] & 0xf0) == 0xe0)
|
|
{
|
|
return (TRUE);
|
|
}
|
|
break;
|
|
case AT_IPv6:
|
|
if (addr_data[0] == 0xff)
|
|
{
|
|
return (TRUE);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return (FALSE);
|
|
}
|
|
|
|
static char * aeron_format_transport_uri(const aeron_conversation_info_t * cinfo)
|
|
{
|
|
wmem_strbuf_t * uri;
|
|
|
|
uri = wmem_strbuf_new(wmem_packet_scope(), "aeron:udp?");
|
|
if (aeron_is_address_multicast(cinfo->addr2))
|
|
{
|
|
switch (cinfo->addr2->type)
|
|
{
|
|
case AT_IPv6:
|
|
wmem_strbuf_append_printf(uri, "group=[%s]:%" G_GUINT16_FORMAT, address_to_str(wmem_packet_scope(), cinfo->addr2), cinfo->port2);
|
|
break;
|
|
case AT_IPv4:
|
|
default:
|
|
wmem_strbuf_append_printf(uri, "group=%s:%" G_GUINT16_FORMAT, address_to_str(wmem_packet_scope(), cinfo->addr2), cinfo->port2);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (cinfo->addr2->type)
|
|
{
|
|
case AT_IPv6:
|
|
wmem_strbuf_append_printf(uri, "remote=[%s]:%" G_GUINT16_FORMAT, address_to_str(wmem_packet_scope(), cinfo->addr2), cinfo->port2);
|
|
break;
|
|
case AT_IPv4:
|
|
default:
|
|
wmem_strbuf_append_printf(uri, "remote=%s:%" G_GUINT16_FORMAT, address_to_str(wmem_packet_scope(), cinfo->addr2), cinfo->port2);
|
|
break;
|
|
}
|
|
}
|
|
return (wmem_strbuf_finalize(uri));
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Packet definitions. */
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
/* Basic frame offsets */
|
|
#define O_AERON_BASIC_FRAME_LENGTH 0
|
|
#define O_AERON_BASIC_VERSION 4
|
|
#define O_AERON_BASIC_FLAGS 5
|
|
#define O_AERON_BASIC_TYPE 6
|
|
|
|
#define HDR_LENGTH_MIN 12
|
|
|
|
/* Padding frame */
|
|
#define O_AERON_PAD_FRAME_LENGTH 0
|
|
#define O_AERON_PAD_VERSION 4
|
|
#define O_AERON_PAD_FLAGS 5
|
|
#define O_AERON_PAD_TYPE 6
|
|
#define O_AERON_PAD_TERM_OFFSET 8
|
|
#define O_AERON_PAD_SESSION_ID 12
|
|
#define O_AERON_PAD_STREAM_ID 16
|
|
#define O_AERON_PAD_TERM_ID 20
|
|
#define L_AERON_PAD_MIN 24
|
|
|
|
/* Data frame */
|
|
#define O_AERON_DATA_FRAME_LENGTH 0
|
|
#define O_AERON_DATA_VERSION 4
|
|
#define O_AERON_DATA_FLAGS 5
|
|
#define O_AERON_DATA_TYPE 6
|
|
#define O_AERON_DATA_TERM_OFFSET 8
|
|
#define O_AERON_DATA_SESSION_ID 12
|
|
#define O_AERON_DATA_STREAM_ID 16
|
|
#define O_AERON_DATA_TERM_ID 20
|
|
#define O_AERON_DATA_DATA 24
|
|
#define L_AERON_DATA_MIN 24
|
|
|
|
/* NAK frame */
|
|
#define O_AERON_NAK_FRAME_LENGTH 0
|
|
#define O_AERON_NAK_VERSION 4
|
|
#define O_AERON_NAK_FLAGS 5
|
|
#define O_AERON_NAK_TYPE 6
|
|
#define O_AERON_NAK_SESSION_ID 8
|
|
#define O_AERON_NAK_STREAM_ID 12
|
|
#define O_AERON_NAK_TERM_ID 16
|
|
#define O_AERON_NAK_TERM_OFFSET 20
|
|
#define O_AERON_NAK_LENGTH 24
|
|
#define L_AERON_NAK 28
|
|
|
|
/* Status message */
|
|
#define O_AERON_SM_FRAME_LENGTH 0
|
|
#define O_AERON_SM_VERSION 4
|
|
#define O_AERON_SM_FLAGS 5
|
|
#define O_AERON_SM_TYPE 6
|
|
#define O_AERON_SM_SESSION_ID 8
|
|
#define O_AERON_SM_STREAM_ID 12
|
|
#define O_AERON_SM_TERM_ID 16
|
|
#define O_AERON_SM_COMPLETED_TERM_OFFSET 20
|
|
#define O_AERON_SM_RECEIVER_WINDOW 24
|
|
#define O_AERON_SM_FEEDBACK 28
|
|
#define L_AERON_SM_MIN 28
|
|
|
|
/* Error header */
|
|
#define O_AERON_ERR_FRAME_LENGTH 0
|
|
#define O_AERON_ERR_VERSION 4
|
|
#define O_AERON_ERR_CODE 5
|
|
#define O_AERON_ERR_TYPE 6
|
|
#define O_AERON_ERR_OFFENDING_FRAME_LENGTH 8
|
|
#define O_AERON_ERR_OFFENDING_HEADER 12
|
|
#define O_AERON_ERR_TERM_ID 16
|
|
#define O_AERON_ERR_COMPLETED_TERM_OFFSET 20
|
|
#define O_AERON_ERR_RECEIVER_WINDOW 24
|
|
#define O_AERON_ERR_FEEDBACK 28
|
|
#define L_AERON_ERR_MIN 12
|
|
|
|
/* Setup frame */
|
|
#define O_AERON_SETUP_FRAME_LENGTH 0
|
|
#define O_AERON_SETUP_VERSION 4
|
|
#define O_AERON_SETUP_FLAGS 5
|
|
#define O_AERON_SETUP_TYPE 6
|
|
#define O_AERON_SETUP_TERM_OFFSET 8
|
|
#define O_AERON_SETUP_SESSION_ID 12
|
|
#define O_AERON_SETUP_STREAM_ID 16
|
|
#define O_AERON_SETUP_INITIAL_TERM_ID 20
|
|
#define O_AERON_SETUP_ACTIVE_TERM_ID 24
|
|
#define O_AERON_SETUP_TERM_LENGTH 28
|
|
#define O_AERON_SETUP_MTU 32
|
|
#define L_AERON_SETUP 36
|
|
|
|
#define HDR_TYPE_PAD 0x0000
|
|
#define HDR_TYPE_DATA 0x0001
|
|
#define HDR_TYPE_NAK 0x0002
|
|
#define HDR_TYPE_SM 0x0003
|
|
#define HDR_TYPE_ERR 0x0004
|
|
#define HDR_TYPE_SETUP 0x0005
|
|
#define HDR_TYPE_EXT 0xFFFF
|
|
|
|
#define DATA_FLAGS_BEGIN 0x80
|
|
#define DATA_FLAGS_END 0x40
|
|
#define DATA_FLAGS_COMPLETE (DATA_FLAGS_BEGIN | DATA_FLAGS_END)
|
|
|
|
#define STATUS_FLAGS_SETUP 0x80
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Value translation tables. */
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
static const value_string aeron_frame_type[] =
|
|
{
|
|
{ HDR_TYPE_PAD, "Pad" },
|
|
{ HDR_TYPE_DATA, "Data" },
|
|
{ HDR_TYPE_NAK, "NAK" },
|
|
{ HDR_TYPE_SM, "Status" },
|
|
{ HDR_TYPE_ERR, "Error" },
|
|
{ HDR_TYPE_SETUP, "Setup" },
|
|
{ HDR_TYPE_EXT, "Extension" },
|
|
{ 0x0, NULL }
|
|
};
|
|
|
|
/*
|
|
Aeron conversations:
|
|
|
|
UDP unicast:
|
|
- The URL specifies the subscriber address and UDP port, and the publisher "connects" to the single subscriber.
|
|
- The publisher sends Pad, Data, and Setup frames to the subscriber address and port.
|
|
- The subscriber sends NAK and SM frames to the publisher, using as the destination the address and port from
|
|
which the Setup and Data frames were received
|
|
- So the conversation is defined by [A(publisher),A(subscriber),P(publisher),P(subscriber),PT_UDP]
|
|
|
|
UDP multicast:
|
|
- The URL specifies the data multicast group and UDP port, and must be an odd-numbered address. The control multicast
|
|
group is automatically set to be one greater than the data multicast group, and the same port is used.
|
|
- The publisher sends Pad, Data, and Setup frames to the data multicast group and port.
|
|
- The subscriber sends NAK and SM frames to the control multicast group and port.
|
|
- So the conversation is defined by [ControlGroup,DataGroup,port,port,PT_UDP]
|
|
|
|
*/
|
|
|
|
static aeron_conversation_info_t * aeron_setup_conversation_info(const packet_info * pinfo, guint16 type)
|
|
{
|
|
aeron_conversation_info_t * cinfo;
|
|
int addr_len = pinfo->dst.len;
|
|
|
|
cinfo = wmem_new0(wmem_packet_scope(), aeron_conversation_info_t);
|
|
switch (pinfo->dst.type)
|
|
{
|
|
case AT_IPv4:
|
|
{
|
|
const guint8 * dst_addr = (const guint8 *) pinfo->dst.data;
|
|
|
|
cinfo->addr1 = wmem_new0(wmem_packet_scope(), address);
|
|
cinfo->addr2 = wmem_new0(wmem_packet_scope(), address);
|
|
if (aeron_is_address_multicast(&(pinfo->dst)))
|
|
{
|
|
guint8 * addr1;
|
|
guint8 * addr2;
|
|
|
|
addr1 = (guint8 *) wmem_memdup(wmem_packet_scope(), (const void *) dst_addr, (size_t) addr_len);
|
|
addr2 = (guint8 *) wmem_memdup(wmem_packet_scope(), (const void *) dst_addr, (size_t) addr_len);
|
|
if ((dst_addr[addr_len - 1] & 0x1) != 0)
|
|
{
|
|
/* Address is odd, so it's the data group (in addr2). Increment the last byte of addr1 for the control group. */
|
|
addr1[addr_len - 1]++;
|
|
}
|
|
else
|
|
{
|
|
/* Address is even, so it's the control group (in addr1). Decrement the last byte of addr2 for the data group. */
|
|
addr2[addr_len - 1]--;
|
|
}
|
|
set_address(cinfo->addr1, AT_IPv4, addr_len, (void *) addr1);
|
|
set_address(cinfo->addr2, AT_IPv4, addr_len, (void *) addr2);
|
|
cinfo->port1 = pinfo->destport;
|
|
cinfo->port2 = cinfo->port1;
|
|
}
|
|
else
|
|
{
|
|
switch (type)
|
|
{
|
|
case HDR_TYPE_PAD:
|
|
case HDR_TYPE_DATA:
|
|
case HDR_TYPE_SETUP:
|
|
/* Destination is a receiver */
|
|
copy_address_wmem(wmem_packet_scope(), cinfo->addr1, &(pinfo->src));
|
|
cinfo->port1 = pinfo->srcport;
|
|
copy_address_wmem(wmem_packet_scope(), cinfo->addr2, &(pinfo->dst));
|
|
cinfo->port2 = pinfo->destport;
|
|
break;
|
|
case HDR_TYPE_NAK:
|
|
case HDR_TYPE_SM:
|
|
/* Destination is the source */
|
|
copy_address_wmem(wmem_packet_scope(), cinfo->addr1, &(pinfo->dst));
|
|
cinfo->port1 = pinfo->destport;
|
|
copy_address_wmem(wmem_packet_scope(), cinfo->addr2, &(pinfo->src));
|
|
cinfo->port2 = pinfo->srcport;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case AT_IPv6:
|
|
{
|
|
const guint8 * dst_addr = (const guint8 *) pinfo->dst.data;
|
|
|
|
cinfo->addr1 = wmem_new0(wmem_packet_scope(), address);
|
|
cinfo->addr2 = wmem_new0(wmem_packet_scope(), address);
|
|
if (aeron_is_address_multicast(&(pinfo->dst)))
|
|
{
|
|
guint8 * addr1;
|
|
guint8 * addr2;
|
|
|
|
addr1 = (guint8 *) wmem_memdup(wmem_packet_scope(), (const void *) dst_addr, (size_t) addr_len);
|
|
addr2 = (guint8 *) wmem_memdup(wmem_packet_scope(), (const void *) dst_addr, (size_t) addr_len);
|
|
if ((dst_addr[addr_len - 1] & 0x1) != 0)
|
|
{
|
|
/* Address is odd, so it's the data group (in addr2). Increment the last byte of addr1 for the control group. */
|
|
addr1[addr_len - 1]++;
|
|
}
|
|
else
|
|
{
|
|
/* Address is even, so it's the control group (in addr1). Decrement the last byte of addr2 for the data group. */
|
|
addr2[addr_len - 1]--;
|
|
}
|
|
set_address(cinfo->addr1, AT_IPv6, addr_len, (void *) addr1);
|
|
set_address(cinfo->addr2, AT_IPv6, addr_len, (void *) addr2);
|
|
cinfo->port1 = pinfo->destport;
|
|
cinfo->port2 = cinfo->port1;
|
|
}
|
|
else
|
|
{
|
|
switch (type)
|
|
{
|
|
case HDR_TYPE_PAD:
|
|
case HDR_TYPE_DATA:
|
|
case HDR_TYPE_SETUP:
|
|
/* Destination is a receiver */
|
|
copy_address_wmem(wmem_packet_scope(), cinfo->addr1, &(pinfo->src));
|
|
cinfo->port1 = pinfo->srcport;
|
|
copy_address_wmem(wmem_packet_scope(), cinfo->addr2, &(pinfo->dst));
|
|
cinfo->port2 = pinfo->destport;
|
|
break;
|
|
case HDR_TYPE_NAK:
|
|
case HDR_TYPE_SM:
|
|
/* Destination is the source */
|
|
copy_address_wmem(wmem_packet_scope(), cinfo->addr1, &(pinfo->dst));
|
|
cinfo->port1 = pinfo->destport;
|
|
copy_address_wmem(wmem_packet_scope(), cinfo->addr2, &(pinfo->src));
|
|
cinfo->port2 = pinfo->srcport;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
return (NULL);
|
|
}
|
|
return (cinfo);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Handles of all types. */
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
/* Dissector tree handles */
|
|
static gint ett_aeron = -1;
|
|
static gint ett_aeron_pad = -1;
|
|
static gint ett_aeron_data = -1;
|
|
static gint ett_aeron_data_flags = -1;
|
|
static gint ett_aeron_data_reassembly = -1;
|
|
static gint ett_aeron_nak = -1;
|
|
static gint ett_aeron_sm = -1;
|
|
static gint ett_aeron_sm_flags = -1;
|
|
static gint ett_aeron_err = -1;
|
|
static gint ett_aeron_setup = -1;
|
|
static gint ett_aeron_ext = -1;
|
|
static gint ett_aeron_sequence_analysis = -1;
|
|
static gint ett_aeron_sequence_analysis_retransmission_rx = -1;
|
|
static gint ett_aeron_sequence_analysis_nak_rx = -1;
|
|
static gint ett_aeron_sequence_analysis_term_offset = -1;
|
|
static gint ett_aeron_stream_analysis = -1;
|
|
|
|
/* Dissector field handles */
|
|
static int hf_aeron_channel_id = -1;
|
|
static int hf_aeron_pad = -1;
|
|
static int hf_aeron_pad_frame_length = -1;
|
|
static int hf_aeron_pad_version = -1;
|
|
static int hf_aeron_pad_flags = -1;
|
|
static int hf_aeron_pad_type = -1;
|
|
static int hf_aeron_pad_term_offset = -1;
|
|
static int hf_aeron_pad_session_id = -1;
|
|
static int hf_aeron_pad_stream_id = -1;
|
|
static int hf_aeron_pad_term_id = -1;
|
|
static int hf_aeron_data = -1;
|
|
static int hf_aeron_data_frame_length = -1;
|
|
static int hf_aeron_data_version = -1;
|
|
static int hf_aeron_data_flags = -1;
|
|
static int hf_aeron_data_flags_b = -1;
|
|
static int hf_aeron_data_flags_e = -1;
|
|
static int hf_aeron_data_type = -1;
|
|
static int hf_aeron_data_term_offset = -1;
|
|
static int hf_aeron_data_next_offset = -1;
|
|
static int hf_aeron_data_next_offset_term = -1;
|
|
static int hf_aeron_data_next_offset_first_frame = -1;
|
|
static int hf_aeron_data_session_id = -1;
|
|
static int hf_aeron_data_stream_id = -1;
|
|
static int hf_aeron_data_term_id = -1;
|
|
static int hf_aeron_data_reassembly = -1;
|
|
static int hf_aeron_data_reassembly_fragment = -1;
|
|
static int hf_aeron_nak = -1;
|
|
static int hf_aeron_nak_frame_length = -1;
|
|
static int hf_aeron_nak_version = -1;
|
|
static int hf_aeron_nak_flags = -1;
|
|
static int hf_aeron_nak_type = -1;
|
|
static int hf_aeron_nak_session_id = -1;
|
|
static int hf_aeron_nak_stream_id = -1;
|
|
static int hf_aeron_nak_term_id = -1;
|
|
static int hf_aeron_nak_term_offset = -1;
|
|
static int hf_aeron_nak_length = -1;
|
|
static int hf_aeron_sm = -1;
|
|
static int hf_aeron_sm_frame_length = -1;
|
|
static int hf_aeron_sm_version = -1;
|
|
static int hf_aeron_sm_flags = -1;
|
|
static int hf_aeron_sm_flags_s = -1;
|
|
static int hf_aeron_sm_type = -1;
|
|
static int hf_aeron_sm_session_id = -1;
|
|
static int hf_aeron_sm_stream_id = -1;
|
|
static int hf_aeron_sm_consumption_term_id = -1;
|
|
static int hf_aeron_sm_consumption_term_offset = -1;
|
|
static int hf_aeron_sm_receiver_window = -1;
|
|
static int hf_aeron_sm_feedback = -1;
|
|
static int hf_aeron_err = -1;
|
|
static int hf_aeron_err_frame_length = -1;
|
|
static int hf_aeron_err_version = -1;
|
|
static int hf_aeron_err_code = -1;
|
|
static int hf_aeron_err_type = -1;
|
|
static int hf_aeron_err_off_frame_length = -1;
|
|
static int hf_aeron_err_off_hdr = -1;
|
|
static int hf_aeron_err_string = -1;
|
|
static int hf_aeron_setup = -1;
|
|
static int hf_aeron_setup_frame_length = -1;
|
|
static int hf_aeron_setup_version = -1;
|
|
static int hf_aeron_setup_flags = -1;
|
|
static int hf_aeron_setup_type = -1;
|
|
static int hf_aeron_setup_term_offset = -1;
|
|
static int hf_aeron_setup_session_id = -1;
|
|
static int hf_aeron_setup_stream_id = -1;
|
|
static int hf_aeron_setup_initial_term_id = -1;
|
|
static int hf_aeron_setup_active_term_id = -1;
|
|
static int hf_aeron_setup_term_length = -1;
|
|
static int hf_aeron_setup_mtu = -1;
|
|
static int hf_aeron_sequence_analysis = -1;
|
|
static int hf_aeron_sequence_analysis_channel_prev_frame = -1;
|
|
static int hf_aeron_sequence_analysis_channel_next_frame = -1;
|
|
static int hf_aeron_sequence_analysis_stream_prev_frame = -1;
|
|
static int hf_aeron_sequence_analysis_stream_next_frame = -1;
|
|
static int hf_aeron_sequence_analysis_term_prev_frame = -1;
|
|
static int hf_aeron_sequence_analysis_term_next_frame = -1;
|
|
static int hf_aeron_sequence_analysis_term_offset = -1;
|
|
static int hf_aeron_sequence_analysis_term_offset_frame = -1;
|
|
static int hf_aeron_sequence_analysis_retransmission = -1;
|
|
static int hf_aeron_sequence_analysis_retransmission_rx = -1;
|
|
static int hf_aeron_sequence_analysis_retransmission_rx_frame = -1;
|
|
static int hf_aeron_sequence_analysis_keepalive = -1;
|
|
static int hf_aeron_sequence_analysis_nak_unrecovered = -1;
|
|
static int hf_aeron_sequence_analysis_nak_rx = -1;
|
|
static int hf_aeron_sequence_analysis_nak_rx_frame = -1;
|
|
static int hf_aeron_stream_analysis = -1;
|
|
static int hf_aeron_stream_analysis_high_term_id = -1;
|
|
static int hf_aeron_stream_analysis_high_term_offset = -1;
|
|
static int hf_aeron_stream_analysis_completed_term_id = -1;
|
|
static int hf_aeron_stream_analysis_completed_term_offset = -1;
|
|
static int hf_aeron_stream_analysis_outstanding_bytes = -1;
|
|
|
|
/* Expert info handles */
|
|
static expert_field ei_aeron_analysis_nak = EI_INIT;
|
|
static expert_field ei_aeron_analysis_window_full = EI_INIT;
|
|
static expert_field ei_aeron_analysis_idle_rx = EI_INIT;
|
|
static expert_field ei_aeron_analysis_pacing_rx = EI_INIT;
|
|
static expert_field ei_aeron_analysis_ooo = EI_INIT;
|
|
static expert_field ei_aeron_analysis_ooo_gap = EI_INIT;
|
|
static expert_field ei_aeron_analysis_keepalive = EI_INIT;
|
|
static expert_field ei_aeron_analysis_ooo_sm = EI_INIT;
|
|
static expert_field ei_aeron_analysis_keepalive_sm = EI_INIT;
|
|
static expert_field ei_aeron_analysis_window_resize = EI_INIT;
|
|
static expert_field ei_aeron_analysis_rx = EI_INIT;
|
|
static expert_field ei_aeron_analysis_term_id_change = EI_INIT;
|
|
static expert_field ei_aeron_analysis_invalid_pad_length = EI_INIT;
|
|
static expert_field ei_aeron_analysis_invalid_data_length = EI_INIT;
|
|
static expert_field ei_aeron_analysis_invalid_nak_length = EI_INIT;
|
|
static expert_field ei_aeron_analysis_invalid_sm_length = EI_INIT;
|
|
static expert_field ei_aeron_analysis_invalid_err_length = EI_INIT;
|
|
static expert_field ei_aeron_analysis_invalid_setup_length = EI_INIT;
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Setup packet information */
|
|
/*----------------------------------------------------------------------------*/
|
|
typedef struct
|
|
{
|
|
guint32 info_flags;
|
|
guint32 stream_id;
|
|
guint32 term_id;
|
|
guint32 term_offset;
|
|
guint32 length;
|
|
guint32 data_length;
|
|
guint32 receiver_window;
|
|
guint32 nak_term_offset;
|
|
guint32 nak_length;
|
|
guint16 type;
|
|
guint8 flags;
|
|
} aeron_packet_info_t;
|
|
#define AERON_PACKET_INFO_FLAGS_STREAM_ID_VALID 0x00000001
|
|
#define AERON_PACKET_INFO_FLAGS_TERM_ID_VALID 0x00000002
|
|
#define AERON_PACKET_INFO_FLAGS_TERM_OFFSET_VALID 0x00000004
|
|
|
|
static void aeron_frame_nak_rx_add(aeron_frame_info_t * nak_info, aeron_frame_info_t * rx_info, guint32 term_offset, guint32 length)
|
|
{
|
|
if (nak_info->nak_analysis->unrecovered_length >= length)
|
|
{
|
|
wmem_list_frame_t * lf = wmem_list_head(nak_info->nak_analysis->rx);
|
|
aeron_rx_info_t * rx = NULL;
|
|
|
|
while (lf != NULL)
|
|
{
|
|
rx = (aeron_rx_info_t *) wmem_list_frame_data(lf);
|
|
if (rx != NULL)
|
|
{
|
|
if ((rx->term_offset == term_offset) && (rx->length == length))
|
|
{
|
|
/* Already have this RX */
|
|
return;
|
|
}
|
|
}
|
|
lf = wmem_list_frame_next(lf);
|
|
}
|
|
/* This RX frame isn't in the list, so add it */
|
|
rx = wmem_new0(wmem_file_scope(), aeron_rx_info_t);
|
|
rx->frame_info = rx_info;
|
|
rx->term_offset = term_offset;
|
|
rx->length = length;
|
|
wmem_list_append(nak_info->nak_analysis->rx, (void *) rx);
|
|
nak_info->nak_analysis->unrecovered_length -= length;
|
|
wmem_list_append(rx_info->rx, (void *) nak_info);
|
|
}
|
|
}
|
|
|
|
static void aeron_frame_process_rx(aeron_packet_info_t * info, aeron_frame_info_t * finfo, aeron_term_t * term)
|
|
{
|
|
wmem_list_frame_t * lf;
|
|
|
|
lf = wmem_list_head(term->nak);
|
|
while (lf != NULL)
|
|
{
|
|
aeron_nak_t * nak = (aeron_nak_t *) wmem_list_frame_data(lf);
|
|
if (nak != NULL)
|
|
{
|
|
if (nak->frame_info->frame <= finfo->frame)
|
|
{
|
|
if ((nak->term_offset <= info->term_offset) && (nak->length >= info->length))
|
|
{
|
|
/* This data frame falls entirely within the NAK range */
|
|
aeron_frame_nak_rx_add(nak->frame_info, finfo, info->term_offset, info->length);
|
|
}
|
|
}
|
|
}
|
|
lf = wmem_list_frame_next(lf);
|
|
}
|
|
}
|
|
|
|
static void aeron_frame_nak_analysis_setup(aeron_packet_info_t * info, aeron_frame_info_t * finfo, aeron_term_t * term)
|
|
{
|
|
aeron_nak_t * nak = wmem_new0(wmem_file_scope(), aeron_nak_t);
|
|
nak->term = term;
|
|
nak->frame_info = finfo;
|
|
nak->term_offset = info->nak_term_offset;
|
|
nak->length = info->nak_length;
|
|
wmem_list_append(term->nak, (void *) nak);
|
|
|
|
finfo->nak_analysis = wmem_new0(wmem_file_scope(), aeron_nak_analysis_t);
|
|
finfo->nak_analysis->frame_info = finfo;
|
|
finfo->nak_analysis->rx = wmem_list_new(wmem_file_scope());
|
|
finfo->nak_analysis->nak_term_offset = info->nak_term_offset;
|
|
finfo->nak_analysis->nak_length = info->nak_length;
|
|
finfo->nak_analysis->unrecovered_length = info->nak_length;
|
|
}
|
|
|
|
/* return 0 for success and -1 for error */
|
|
static int aeron_frame_stream_analysis_setup(packet_info * pinfo, aeron_packet_info_t * info, aeron_frame_info_t * finfo, aeron_stream_t * stream, aeron_term_t * term, gboolean new_term)
|
|
{
|
|
aeron_stream_rcv_t * rcv = NULL;
|
|
/* dp is the current data position (from this frame). */
|
|
aeron_pos_t dp = { 0, 0 };
|
|
/*
|
|
pdp is the previous (high) data position (from the stream).
|
|
pdpv is TRUE if pdp is valid (meaning we previously saw a data message).
|
|
*/
|
|
aeron_pos_t pdp = stream->high;
|
|
gboolean pdpv = ((stream->flags & AERON_STREAM_FLAGS_HIGH_VALID) != 0);
|
|
/* rp is the current receiver position (from this frame). */
|
|
aeron_pos_t rp = { 0, 0 };
|
|
/*
|
|
prp is the previous (high) receiver completed position (from the stream receiver).
|
|
prpv is TRUE if prp is valid (meaning we previously saw a status message).
|
|
*/
|
|
aeron_pos_t prp = { 0, 0 };
|
|
gboolean prpv = FALSE;
|
|
guint32 cur_receiver_window = 0;
|
|
/* Flags to be used when creating the fragment frame entry */
|
|
guint32 frame_flags = 0;
|
|
|
|
if (info->type == HDR_TYPE_SM)
|
|
{
|
|
/* Locate the receiver */
|
|
rcv = aeron_stream_rcv_find(stream, &(pinfo->src), pinfo->srcport);
|
|
if (rcv == NULL)
|
|
{
|
|
rcv = aeron_stream_rcv_add(stream, &(pinfo->src), pinfo->srcport);
|
|
}
|
|
else
|
|
{
|
|
prpv = TRUE;
|
|
prp = rcv->completed;
|
|
cur_receiver_window = rcv->receiver_window;
|
|
}
|
|
}
|
|
switch (info->type)
|
|
{
|
|
case HDR_TYPE_DATA:
|
|
case HDR_TYPE_PAD:
|
|
dp.term_id = info->term_id;
|
|
dp.term_offset = info->term_offset;
|
|
if (!aeron_pos_add_length(&dp, info->length, stream->term_length))
|
|
return -1;
|
|
if (pdpv)
|
|
{
|
|
if (dp.term_id > stream->high.term_id)
|
|
{
|
|
stream->high.term_id = dp.term_id;
|
|
stream->high.term_offset = dp.term_offset;
|
|
}
|
|
else if (dp.term_offset > stream->high.term_offset)
|
|
{
|
|
stream->high.term_offset = dp.term_offset;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
stream->flags |= AERON_STREAM_FLAGS_HIGH_VALID;
|
|
stream->high.term_id = dp.term_id;
|
|
stream->high.term_offset = dp.term_offset;
|
|
}
|
|
break;
|
|
case HDR_TYPE_SM:
|
|
rp.term_id = info->term_id;
|
|
rp.term_offset = info->term_offset;
|
|
if (prpv)
|
|
{
|
|
if (rp.term_id > rcv->completed.term_id)
|
|
{
|
|
rcv->completed.term_id = rp.term_id;
|
|
rcv->completed.term_offset = rp.term_offset;
|
|
}
|
|
else if (rp.term_offset > rcv->completed.term_offset)
|
|
{
|
|
rcv->completed.term_offset = rp.term_offset;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rcv->completed.term_id = rp.term_id;
|
|
rcv->completed.term_offset = rp.term_offset;
|
|
}
|
|
rcv->receiver_window = info->receiver_window;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (aeron_stream_analysis)
|
|
{
|
|
if ((stream->flags & AERON_STREAM_FLAGS_HIGH_VALID) != 0)
|
|
{
|
|
finfo->stream_analysis = wmem_new0(wmem_file_scope(), aeron_stream_analysis_t);
|
|
}
|
|
}
|
|
if (finfo->stream_analysis != NULL)
|
|
{
|
|
switch (info->type)
|
|
{
|
|
case HDR_TYPE_DATA:
|
|
case HDR_TYPE_SM:
|
|
case HDR_TYPE_PAD:
|
|
finfo->stream_analysis->high.term_id = stream->high.term_id;
|
|
finfo->stream_analysis->high.term_offset = stream->high.term_offset;
|
|
if (rcv != NULL)
|
|
{
|
|
finfo->stream_analysis->flags2 |= AERON_STREAM_ANALYSIS_FLAGS2_RCV_VALID;
|
|
finfo->stream_analysis->completed.term_id = rcv->completed.term_id;
|
|
finfo->stream_analysis->completed.term_offset = rcv->completed.term_offset;
|
|
finfo->stream_analysis->receiver_window = rcv->receiver_window;
|
|
finfo->stream_analysis->outstanding_bytes = aeron_pos_delta(&(finfo->stream_analysis->high), &(finfo->stream_analysis->completed), stream->term_length);
|
|
if (finfo->stream_analysis->outstanding_bytes >= finfo->stream_analysis->receiver_window)
|
|
{
|
|
finfo->stream_analysis->flags |= AERON_STREAM_ANALYSIS_FLAGS_WINDOW_FULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
finfo->stream_analysis->completed.term_id = 0;
|
|
finfo->stream_analysis->completed.term_offset = 0;
|
|
finfo->stream_analysis->receiver_window = 0;
|
|
finfo->stream_analysis->outstanding_bytes = 0;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
switch (info->type)
|
|
{
|
|
case HDR_TYPE_DATA:
|
|
case HDR_TYPE_PAD:
|
|
if (pdpv)
|
|
{
|
|
/* We have a previous data position. */
|
|
int rc = aeron_pos_compare(&dp, &pdp);
|
|
if (rc == 0)
|
|
{
|
|
/* Data position is the same as previous data position. */
|
|
if (info->length == 0)
|
|
{
|
|
finfo->stream_analysis->flags |= AERON_STREAM_ANALYSIS_FLAGS_KEEPALIVE;
|
|
frame_flags |= AERON_FRAME_INFO_FLAGS_KEEPALIVE;
|
|
}
|
|
else
|
|
{
|
|
if (prpv)
|
|
{
|
|
/* Previous receiver position is valid */
|
|
if (aeron_pos_compare(&dp, &prp) == 0)
|
|
{
|
|
finfo->stream_analysis->flags |= AERON_STREAM_ANALYSIS_FLAGS_IDLE_RX;
|
|
}
|
|
else
|
|
{
|
|
finfo->stream_analysis->flags |= AERON_STREAM_ANALYSIS_FLAGS_PACING_RX;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
finfo->stream_analysis->flags |= AERON_STREAM_ANALYSIS_FLAGS_IDLE_RX;
|
|
}
|
|
frame_flags |= AERON_FRAME_INFO_FLAGS_RETRANSMISSION;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
aeron_pos_t expected_dp;
|
|
int erc;
|
|
|
|
expected_dp.term_id = pdp.term_id;
|
|
expected_dp.term_offset = pdp.term_offset;
|
|
if (!aeron_pos_add_length(&expected_dp, info->length, stream->term_length))
|
|
return -1;
|
|
erc = aeron_pos_compare(&expected_dp, &dp);
|
|
if (erc > 0)
|
|
{
|
|
/* Could be OOO - but for now assume it's a RX */
|
|
finfo->stream_analysis->flags |= AERON_STREAM_ANALYSIS_FLAGS_RX;
|
|
frame_flags |= AERON_FRAME_INFO_FLAGS_RETRANSMISSION;
|
|
aeron_frame_process_rx(info, finfo, term);
|
|
}
|
|
else if (erc < 0)
|
|
{
|
|
finfo->stream_analysis->flags |= AERON_STREAM_ANALYSIS_FLAGS_OOO_GAP;
|
|
}
|
|
}
|
|
}
|
|
if (new_term && (info->term_offset == 0))
|
|
{
|
|
finfo->stream_analysis->flags |= AERON_STREAM_ANALYSIS_FLAGS_TERM_ID_CHANGE;
|
|
}
|
|
break;
|
|
case HDR_TYPE_SM:
|
|
if (prpv)
|
|
{
|
|
int rc = aeron_pos_compare(&rp, &prp);
|
|
if (rc == 0)
|
|
{
|
|
/* Completed term ID and term offset stayed the same. */
|
|
finfo->stream_analysis->flags |= AERON_STREAM_ANALYSIS_FLAGS_KEEPALIVE_SM;
|
|
}
|
|
else if (rc < 0)
|
|
{
|
|
finfo->stream_analysis->flags |= AERON_STREAM_ANALYSIS_FLAGS_OOO_SM;
|
|
}
|
|
if (cur_receiver_window != finfo->stream_analysis->receiver_window)
|
|
{
|
|
finfo->stream_analysis->flags |= AERON_STREAM_ANALYSIS_FLAGS_WINDOW_RESIZE;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if ((info->type == HDR_TYPE_DATA) || (info->type == HDR_TYPE_PAD))
|
|
{
|
|
aeron_fragment_t * fragment;
|
|
|
|
fragment = aeron_term_fragment_find(term, info->term_offset);
|
|
if (fragment == NULL)
|
|
{
|
|
fragment = aeron_term_fragment_add(term, info->term_offset, info->length, info->data_length);
|
|
}
|
|
aeron_fragment_frame_add(fragment, finfo, frame_flags, info->length);
|
|
}
|
|
else
|
|
{
|
|
aeron_term_frame_add(term, finfo, frame_flags);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* return 0 for success and -1 for error */
|
|
static int aeron_frame_info_setup(packet_info * pinfo, aeron_transport_t * transport, aeron_packet_info_t * info, aeron_frame_info_t * finfo)
|
|
{
|
|
if (!transport || !aeron_sequence_analysis || !finfo || PINFO_FD_VISITED(pinfo))
|
|
/* XXX - is it an error if transport, aeron_sequence_analysis or finfo are NULL? */
|
|
return 0;
|
|
|
|
if ((info->info_flags & AERON_PACKET_INFO_FLAGS_STREAM_ID_VALID) != 0)
|
|
{
|
|
aeron_stream_t * stream;
|
|
|
|
stream = aeron_transport_stream_find(transport, info->stream_id);
|
|
if (stream == NULL)
|
|
{
|
|
stream = aeron_transport_stream_add(transport, info->stream_id);
|
|
}
|
|
if ((info->info_flags & AERON_PACKET_INFO_FLAGS_TERM_ID_VALID) != 0)
|
|
{
|
|
aeron_term_t * term;
|
|
gboolean new_term = FALSE;
|
|
|
|
term = aeron_stream_term_find(stream, info->term_id);
|
|
if (term == NULL)
|
|
{
|
|
term = aeron_stream_term_add(stream, info->term_id);
|
|
new_term = TRUE;
|
|
}
|
|
if ((info->info_flags & AERON_PACKET_INFO_FLAGS_TERM_OFFSET_VALID) != 0)
|
|
{
|
|
if (aeron_frame_stream_analysis_setup(pinfo, info, finfo, stream, term, new_term) < 0)
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
aeron_term_frame_add(term, finfo, 0);
|
|
if (info->type == HDR_TYPE_NAK)
|
|
{
|
|
aeron_frame_nak_analysis_setup(info, finfo, term);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
aeron_stream_frame_add(stream, finfo, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
aeron_transport_frame_add(transport, finfo, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void aeron_sequence_report_frame(tvbuff_t * tvb, proto_tree * tree, aeron_frame_info_t * finfo)
|
|
{
|
|
proto_item * item = NULL;
|
|
|
|
if ((finfo->flags & AERON_FRAME_INFO_FLAGS_RETRANSMISSION) != 0)
|
|
{
|
|
item = proto_tree_add_uint_format_value(tree, hf_aeron_sequence_analysis_term_offset_frame, tvb, 0, 0, finfo->frame, "%" G_GUINT32_FORMAT " (RX)", finfo->frame);
|
|
}
|
|
else if ((finfo->flags & AERON_FRAME_INFO_FLAGS_KEEPALIVE) != 0)
|
|
{
|
|
item = proto_tree_add_uint_format_value(tree, hf_aeron_sequence_analysis_term_offset_frame, tvb, 0, 0, finfo->frame, "%" G_GUINT32_FORMAT " (KA)", finfo->frame);
|
|
}
|
|
else
|
|
{
|
|
item = proto_tree_add_uint(tree, hf_aeron_sequence_analysis_term_offset_frame, tvb, 0, 0, finfo->frame);
|
|
}
|
|
PROTO_ITEM_SET_GENERATED(item);
|
|
}
|
|
|
|
static void aeron_sequence_report(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, aeron_transport_t * transport, aeron_packet_info_t * info, aeron_frame_info_t * finfo)
|
|
{
|
|
if (transport != NULL)
|
|
{
|
|
if (aeron_sequence_analysis && (finfo != NULL))
|
|
{
|
|
proto_tree * subtree;
|
|
proto_item * item;
|
|
|
|
item = proto_tree_add_item(tree, hf_aeron_sequence_analysis, tvb, 0, 0, ENC_NA);
|
|
PROTO_ITEM_SET_GENERATED(item);
|
|
subtree = proto_item_add_subtree(item, ett_aeron_sequence_analysis);
|
|
if (finfo->transport.previous != 0)
|
|
{
|
|
item = proto_tree_add_uint(subtree, hf_aeron_sequence_analysis_channel_prev_frame, tvb, 0, 0, finfo->transport.previous);
|
|
PROTO_ITEM_SET_GENERATED(item);
|
|
}
|
|
if (finfo->transport.next != 0)
|
|
{
|
|
item = proto_tree_add_uint(subtree, hf_aeron_sequence_analysis_channel_next_frame, tvb, 0, 0, finfo->transport.next);
|
|
PROTO_ITEM_SET_GENERATED(item);
|
|
}
|
|
if ((info->info_flags & AERON_PACKET_INFO_FLAGS_STREAM_ID_VALID) != 0)
|
|
{
|
|
aeron_stream_t * stream;
|
|
|
|
stream = aeron_transport_stream_find(transport, info->stream_id);
|
|
if (stream != NULL)
|
|
{
|
|
if (finfo->stream.previous != 0)
|
|
{
|
|
item = proto_tree_add_uint(subtree, hf_aeron_sequence_analysis_stream_prev_frame, tvb, 0, 0, finfo->stream.previous);
|
|
PROTO_ITEM_SET_GENERATED(item);
|
|
}
|
|
if (finfo->stream.next != 0)
|
|
{
|
|
item = proto_tree_add_uint(subtree, hf_aeron_sequence_analysis_stream_next_frame, tvb, 0, 0, finfo->stream.next);
|
|
PROTO_ITEM_SET_GENERATED(item);
|
|
}
|
|
if ((info->info_flags & AERON_PACKET_INFO_FLAGS_TERM_ID_VALID) != 0)
|
|
{
|
|
aeron_term_t * term;
|
|
|
|
term = aeron_stream_term_find(stream, info->term_id);
|
|
if (term != NULL)
|
|
{
|
|
if (finfo->term.previous != 0)
|
|
{
|
|
item = proto_tree_add_uint(subtree, hf_aeron_sequence_analysis_term_prev_frame, tvb, 0, 0, finfo->term.previous);
|
|
PROTO_ITEM_SET_GENERATED(item);
|
|
}
|
|
if (finfo->term.next != 0)
|
|
{
|
|
item = proto_tree_add_uint(subtree, hf_aeron_sequence_analysis_term_next_frame, tvb, 0, 0, finfo->term.next);
|
|
PROTO_ITEM_SET_GENERATED(item);
|
|
}
|
|
if ((info->info_flags & AERON_PACKET_INFO_FLAGS_TERM_OFFSET_VALID) != 0)
|
|
{
|
|
if ((info->type == HDR_TYPE_DATA) || (info->type == HDR_TYPE_PAD))
|
|
{
|
|
aeron_fragment_t * fragment;
|
|
|
|
fragment = aeron_term_fragment_find(term, info->term_offset);
|
|
if (fragment != NULL)
|
|
{
|
|
proto_item * fei_item;
|
|
gboolean rx = ((finfo->flags & AERON_FRAME_INFO_FLAGS_RETRANSMISSION) != 0);
|
|
gboolean ka = ((finfo->flags & AERON_FRAME_INFO_FLAGS_KEEPALIVE) != 0);
|
|
|
|
if (fragment->frame_count > 1)
|
|
{
|
|
proto_tree * frame_tree;
|
|
proto_item * frame_item;
|
|
wmem_list_frame_t * lf;
|
|
|
|
frame_item = proto_tree_add_item(subtree, hf_aeron_sequence_analysis_term_offset, tvb, 0, 0, ENC_NA);
|
|
PROTO_ITEM_SET_GENERATED(frame_item);
|
|
frame_tree = proto_item_add_subtree(frame_item, ett_aeron_sequence_analysis_term_offset);
|
|
lf = wmem_list_head(fragment->frame);
|
|
while (lf != NULL)
|
|
{
|
|
aeron_frame_info_t * frag_frame = (aeron_frame_info_t *) wmem_list_frame_data(lf);
|
|
if (frag_frame != NULL)
|
|
{
|
|
if (frag_frame->frame != pinfo->num)
|
|
{
|
|
aeron_sequence_report_frame(tvb, frame_tree, frag_frame);
|
|
}
|
|
}
|
|
lf = wmem_list_frame_next(lf);
|
|
}
|
|
}
|
|
fei_item = proto_tree_add_boolean(subtree, hf_aeron_sequence_analysis_retransmission, tvb, 0, 0, rx);
|
|
PROTO_ITEM_SET_GENERATED(fei_item);
|
|
if (rx)
|
|
{
|
|
if (wmem_list_count(finfo->rx) > 0)
|
|
{
|
|
proto_tree * rx_tree;
|
|
proto_item * rx_item;
|
|
wmem_list_frame_t * lf;
|
|
|
|
rx_item = proto_tree_add_item(subtree, hf_aeron_sequence_analysis_retransmission_rx, tvb, 0, 0, ENC_NA);
|
|
PROTO_ITEM_SET_GENERATED(rx_item);
|
|
rx_tree = proto_item_add_subtree(rx_item, ett_aeron_sequence_analysis_retransmission_rx);
|
|
lf = wmem_list_head(finfo->rx);
|
|
while (lf != NULL)
|
|
{
|
|
aeron_frame_info_t * nak = (aeron_frame_info_t *) wmem_list_frame_data(lf);
|
|
if (nak != NULL)
|
|
{
|
|
rx_item = proto_tree_add_uint(rx_tree, hf_aeron_sequence_analysis_retransmission_rx_frame, tvb, 0, 0, nak->frame);
|
|
PROTO_ITEM_SET_GENERATED(rx_item);
|
|
}
|
|
lf = wmem_list_frame_next(lf);
|
|
}
|
|
}
|
|
}
|
|
fei_item = proto_tree_add_boolean(subtree, hf_aeron_sequence_analysis_keepalive, tvb, 0, 0, ka);
|
|
PROTO_ITEM_SET_GENERATED(fei_item);
|
|
}
|
|
}
|
|
}
|
|
else if ((info->type == HDR_TYPE_NAK) && (finfo->nak_analysis != NULL))
|
|
{
|
|
proto_item * nak_item;
|
|
|
|
nak_item = proto_tree_add_uint(subtree, hf_aeron_sequence_analysis_nak_unrecovered, tvb, 0, 0, finfo->nak_analysis->unrecovered_length);
|
|
PROTO_ITEM_SET_GENERATED(nak_item);
|
|
if (wmem_list_count(finfo->nak_analysis->rx) > 0)
|
|
{
|
|
proto_tree * rx_tree;
|
|
proto_item * rx_item;
|
|
wmem_list_frame_t * lf;
|
|
|
|
rx_item = proto_tree_add_item(subtree, hf_aeron_sequence_analysis_nak_rx, tvb, 0, 0, ENC_NA);
|
|
PROTO_ITEM_SET_GENERATED(rx_item);
|
|
rx_tree = proto_item_add_subtree(rx_item, ett_aeron_sequence_analysis_nak_rx);
|
|
lf = wmem_list_head(finfo->nak_analysis->rx);
|
|
while (lf != NULL)
|
|
{
|
|
aeron_rx_info_t * rx = (aeron_rx_info_t *) wmem_list_frame_data(lf);
|
|
if (rx != NULL)
|
|
{
|
|
rx_item = proto_tree_add_uint_format_value(rx_tree, hf_aeron_sequence_analysis_nak_rx_frame, tvb, 0, 0, rx->frame_info->frame,
|
|
"%" G_GUINT32_FORMAT ", Term offset=%" G_GUINT32_FORMAT " (0x%08x), Length=%" G_GUINT32_FORMAT, rx->frame_info->frame, rx->term_offset, rx->term_offset, rx->length);
|
|
PROTO_ITEM_SET_GENERATED(rx_item);
|
|
}
|
|
lf = wmem_list_frame_next(lf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void aeron_stream_report(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, aeron_transport_t * transport, aeron_frame_info_t * finfo)
|
|
{
|
|
if (transport != NULL)
|
|
{
|
|
if (aeron_sequence_analysis && aeron_stream_analysis && (finfo != NULL) && (finfo->stream_analysis != NULL))
|
|
{
|
|
proto_tree * subtree;
|
|
proto_item * item;
|
|
|
|
item = proto_tree_add_item(tree, hf_aeron_stream_analysis, tvb, 0, 0, ENC_NA);
|
|
PROTO_ITEM_SET_GENERATED(item);
|
|
subtree = proto_item_add_subtree(item, ett_aeron_stream_analysis);
|
|
item = proto_tree_add_uint(subtree, hf_aeron_stream_analysis_high_term_id, tvb, 0, 0, finfo->stream_analysis->high.term_id);
|
|
if ((finfo->stream_analysis->flags & AERON_STREAM_ANALYSIS_FLAGS_TERM_ID_CHANGE) != 0)
|
|
{
|
|
expert_add_info(pinfo, item, &ei_aeron_analysis_term_id_change);
|
|
}
|
|
PROTO_ITEM_SET_GENERATED(item);
|
|
item = proto_tree_add_uint(subtree, hf_aeron_stream_analysis_high_term_offset, tvb, 0, 0, finfo->stream_analysis->high.term_offset);
|
|
PROTO_ITEM_SET_GENERATED(item);
|
|
if ((finfo->stream_analysis->flags & AERON_STREAM_ANALYSIS_FLAGS_IDLE_RX) != 0)
|
|
{
|
|
expert_add_info(pinfo, item, &ei_aeron_analysis_idle_rx);
|
|
}
|
|
if ((finfo->stream_analysis->flags & AERON_STREAM_ANALYSIS_FLAGS_PACING_RX) != 0)
|
|
{
|
|
expert_add_info(pinfo, item, &ei_aeron_analysis_pacing_rx);
|
|
}
|
|
if ((finfo->stream_analysis->flags & AERON_STREAM_ANALYSIS_FLAGS_OOO) != 0)
|
|
{
|
|
expert_add_info(pinfo, item, &ei_aeron_analysis_ooo);
|
|
}
|
|
if ((finfo->stream_analysis->flags & AERON_STREAM_ANALYSIS_FLAGS_OOO_GAP) != 0)
|
|
{
|
|
expert_add_info(pinfo, item, &ei_aeron_analysis_ooo_gap);
|
|
}
|
|
if ((finfo->stream_analysis->flags & AERON_STREAM_ANALYSIS_FLAGS_KEEPALIVE) != 0)
|
|
{
|
|
expert_add_info(pinfo, item, &ei_aeron_analysis_keepalive);
|
|
}
|
|
if ((finfo->stream_analysis->flags & AERON_STREAM_ANALYSIS_FLAGS_RX) != 0)
|
|
{
|
|
expert_add_info(pinfo, item, &ei_aeron_analysis_rx);
|
|
}
|
|
if ((finfo->stream_analysis->flags2 & AERON_STREAM_ANALYSIS_FLAGS2_RCV_VALID) != 0)
|
|
{
|
|
item = proto_tree_add_uint(subtree, hf_aeron_stream_analysis_completed_term_id, tvb, 0, 0, finfo->stream_analysis->completed.term_id);
|
|
PROTO_ITEM_SET_GENERATED(item);
|
|
item = proto_tree_add_uint(subtree, hf_aeron_stream_analysis_completed_term_offset, tvb, 0, 0, finfo->stream_analysis->completed.term_offset);
|
|
PROTO_ITEM_SET_GENERATED(item);
|
|
if ((finfo->stream_analysis->flags & AERON_STREAM_ANALYSIS_FLAGS_OOO_SM) != 0)
|
|
{
|
|
expert_add_info(pinfo, item, &ei_aeron_analysis_ooo_sm);
|
|
}
|
|
if ((finfo->stream_analysis->flags & AERON_STREAM_ANALYSIS_FLAGS_KEEPALIVE_SM) != 0)
|
|
{
|
|
expert_add_info(pinfo, item, &ei_aeron_analysis_keepalive_sm);
|
|
}
|
|
item = proto_tree_add_uint(subtree, hf_aeron_stream_analysis_outstanding_bytes, tvb, 0, 0, finfo->stream_analysis->outstanding_bytes);
|
|
PROTO_ITEM_SET_GENERATED(item);
|
|
if ((finfo->stream_analysis->flags & AERON_STREAM_ANALYSIS_FLAGS_WINDOW_FULL) != 0)
|
|
{
|
|
expert_add_info(pinfo, item, &ei_aeron_analysis_window_full);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void aeron_next_offset_report(tvbuff_t * tvb, proto_tree * tree, aeron_transport_t * transport, guint32 stream_id, guint32 term_id, guint32 term_offset, guint32 length)
|
|
{
|
|
aeron_stream_t * stream;
|
|
|
|
stream = aeron_transport_stream_find(transport, stream_id);
|
|
if (stream != NULL)
|
|
{
|
|
aeron_term_t * term;
|
|
if (stream->term_length == 0)
|
|
{
|
|
stream->term_length = length;
|
|
}
|
|
term = aeron_stream_term_find(stream, term_id);
|
|
if (term != NULL)
|
|
{
|
|
aeron_fragment_t * fragment = aeron_term_fragment_find(term, term_offset);
|
|
if (fragment != NULL)
|
|
{
|
|
guint32 next_offset = term_offset + length;
|
|
guint32 next_offset_term_id = term_id;
|
|
guint32 next_offset_first_frame = 0;
|
|
aeron_term_t * next_offset_term = NULL;
|
|
proto_item * item;
|
|
|
|
if (next_offset >= stream->term_length)
|
|
{
|
|
next_offset = 0;
|
|
next_offset_term_id++;
|
|
}
|
|
item = proto_tree_add_uint(tree, hf_aeron_data_next_offset, tvb, 0, 0, next_offset);
|
|
PROTO_ITEM_SET_GENERATED(item);
|
|
if (next_offset_term_id != term_id)
|
|
{
|
|
next_offset_term = aeron_stream_term_find(stream, next_offset_term_id);
|
|
item = proto_tree_add_uint(tree, hf_aeron_data_next_offset_term, tvb, 0, 0, next_offset_term_id);
|
|
PROTO_ITEM_SET_GENERATED(item);
|
|
}
|
|
else
|
|
{
|
|
next_offset_term = term;
|
|
}
|
|
if (next_offset_term != NULL)
|
|
{
|
|
aeron_fragment_t * next_offset_fragment;
|
|
next_offset_fragment = aeron_term_fragment_find(next_offset_term, next_offset);
|
|
if (next_offset_fragment != NULL)
|
|
{
|
|
if (next_offset_fragment->first_frame != NULL)
|
|
{
|
|
next_offset_first_frame = next_offset_fragment->first_frame->frame;
|
|
item = proto_tree_add_uint(tree, hf_aeron_data_next_offset_first_frame, tvb, 0, 0, next_offset_first_frame);
|
|
PROTO_ITEM_SET_GENERATED(item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void aeron_info_stream_progress_report(packet_info * pinfo, guint16 msgtype, guint8 flags, guint32 term_id, guint32 term_offset, aeron_frame_info_t * finfo)
|
|
{
|
|
const gchar * type_string = val_to_str_const((guint32) msgtype, aeron_frame_type, "Unknown");
|
|
|
|
if (aeron_sequence_analysis && aeron_stream_analysis && (finfo != NULL) && (finfo->stream_analysis != NULL))
|
|
{
|
|
switch (msgtype)
|
|
{
|
|
case HDR_TYPE_PAD:
|
|
case HDR_TYPE_DATA:
|
|
if ((finfo->stream_analysis->flags & AERON_STREAM_ANALYSIS_FLAGS_KEEPALIVE) != 0)
|
|
{
|
|
col_append_sep_fstr(pinfo->cinfo, COL_INFO, ", ", "%s-KA", type_string);
|
|
}
|
|
else
|
|
{
|
|
col_append_sep_fstr(pinfo->cinfo, COL_INFO, ", ", "%s (0x%08x:%" G_GUINT32_FORMAT ")",
|
|
type_string, term_id, term_offset);
|
|
}
|
|
break;
|
|
case HDR_TYPE_SM:
|
|
if ((finfo->stream_analysis->flags & AERON_STREAM_ANALYSIS_FLAGS_KEEPALIVE_SM) != 0)
|
|
{
|
|
col_append_sep_fstr(pinfo->cinfo, COL_INFO, ", ", "%s-KA", type_string);
|
|
}
|
|
else
|
|
{
|
|
if (finfo->stream_analysis->high.term_id == finfo->stream_analysis->completed.term_id)
|
|
{
|
|
col_append_sep_fstr(pinfo->cinfo, COL_INFO, ", ", "%s (%" G_GUINT32_FORMAT "/%" G_GUINT32_FORMAT " [%" G_GUINT32_FORMAT "])",
|
|
type_string, finfo->stream_analysis->high.term_offset, finfo->stream_analysis->completed.term_offset, finfo->stream_analysis->outstanding_bytes);
|
|
}
|
|
else
|
|
{
|
|
col_append_sep_fstr(pinfo->cinfo, COL_INFO, ", ", "%s (0x%08x:%" G_GUINT32_FORMAT "/0x%08x:%" G_GUINT32_FORMAT " [%" G_GUINT32_FORMAT "])",
|
|
type_string, finfo->stream_analysis->high.term_id, finfo->stream_analysis->high.term_offset, finfo->stream_analysis->completed.term_id, finfo->stream_analysis->completed.term_offset, finfo->stream_analysis->outstanding_bytes);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((msgtype == HDR_TYPE_SM) && ((flags & STATUS_FLAGS_SETUP) != 0))
|
|
{
|
|
col_append_sep_fstr(pinfo->cinfo, COL_INFO, ", ", "%s-SETUP", type_string);
|
|
}
|
|
else
|
|
{
|
|
col_append_sep_str(pinfo->cinfo, COL_INFO, ", ", type_string);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Payload reassembly. */
|
|
/*----------------------------------------------------------------------------*/
|
|
struct aeron_msg_fragment_t_stct;
|
|
typedef struct aeron_msg_fragment_t_stct aeron_msg_fragment_t;
|
|
|
|
struct aeron_msg_t_stct
|
|
{
|
|
wmem_list_t * fragment;
|
|
aeron_term_t * term;
|
|
tvbuff_t * reassembled_data;
|
|
guint32 first_fragment_term_offset;
|
|
guint32 next_expected_term_offset;
|
|
guint32 length; /* Total message payload length */
|
|
guint32 frame_length; /* Total length of all message frames accumulated */
|
|
guint32 fragment_count; /* Number of fragments in this message */
|
|
guint32 contiguous_length; /* Number of contiguous frame bytes accumulated for this message */
|
|
guint32 begin_frame; /* Data frame in which the B flag was set */
|
|
guint32 first_frame; /* Lowest-numbered frame which is part of this message */
|
|
guint32 end_frame; /* Data frame in which the E flag was set */
|
|
guint32 last_frame; /* Highest-numbered frame which is part of this message */
|
|
gboolean complete;
|
|
};
|
|
|
|
struct aeron_msg_fragment_t_stct
|
|
{
|
|
gchar * data;
|
|
guint32 term_offset; /* Term offset for entire fragment */
|
|
guint32 frame_length; /* Length of entire frame/fragment */
|
|
guint32 data_length; /* Payload length */
|
|
guint32 frame; /* Frame in which the fragment resides */
|
|
gint frame_offset; /* Offset into the frame for the entire Aeron message */
|
|
guint8 flags; /* Frame data flags */
|
|
};
|
|
|
|
static void aeron_msg_fragment_add(aeron_msg_t * msg, aeron_msg_fragment_t * fragment)
|
|
{
|
|
/* Add the fragment to the message */
|
|
wmem_list_append(msg->fragment, (void *) fragment);
|
|
/* Update the message */
|
|
msg->length += fragment->data_length;
|
|
msg->contiguous_length += fragment->data_length;
|
|
msg->fragment_count++;
|
|
if (msg->first_frame > fragment->frame)
|
|
{
|
|
msg->first_frame = fragment->frame;
|
|
}
|
|
if (msg->last_frame < fragment->frame)
|
|
{
|
|
msg->last_frame = fragment->frame;
|
|
}
|
|
msg->next_expected_term_offset += fragment->frame_length;
|
|
if ((fragment->flags & DATA_FLAGS_END) == DATA_FLAGS_END)
|
|
{
|
|
gchar * buf;
|
|
wmem_list_frame_t * lf;
|
|
size_t ofs = 0;
|
|
size_t accum_len = 0;
|
|
guint32 last_frame_offset = 0;
|
|
gboolean last_frame_found = FALSE;
|
|
aeron_frame_info_t * finfo = NULL;
|
|
|
|
msg->complete = TRUE;
|
|
msg->end_frame = fragment->frame;
|
|
buf = (gchar *) wmem_alloc(wmem_file_scope(), (size_t) msg->length);
|
|
lf = wmem_list_head(msg->fragment);
|
|
while (lf != NULL)
|
|
{
|
|
aeron_msg_fragment_t * cur_frag = (aeron_msg_fragment_t *) wmem_list_frame_data(lf);
|
|
if (cur_frag != NULL)
|
|
{
|
|
if (cur_frag->frame == msg->last_frame)
|
|
{
|
|
last_frame_offset = cur_frag->frame_offset;
|
|
last_frame_found = TRUE;
|
|
}
|
|
memcpy((void *) (buf + ofs), (void *) cur_frag->data, (size_t) cur_frag->data_length);
|
|
ofs += (size_t) cur_frag->data_length;
|
|
accum_len += (size_t) cur_frag->data_length;
|
|
}
|
|
lf = wmem_list_frame_next(lf);
|
|
}
|
|
DISSECTOR_ASSERT(accum_len == (size_t) msg->length);
|
|
DISSECTOR_ASSERT(last_frame_found == TRUE);
|
|
if (last_frame_found)
|
|
{
|
|
finfo = aeron_frame_info_find(msg->last_frame, last_frame_offset);
|
|
}
|
|
msg->reassembled_data = tvb_new_real_data(buf, msg->length, msg->length);
|
|
DISSECTOR_ASSERT(finfo != NULL);
|
|
if (finfo != NULL)
|
|
{
|
|
finfo->flags |= AERON_FRAME_INFO_FLAGS_REASSEMBLED_MSG;
|
|
finfo->message = msg;
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean aeron_msg_process_orphan_fragments_msg_cb(const void *key _U_, void * value, void * userdata)
|
|
{
|
|
aeron_msg_t * msg = (aeron_msg_t *) value;
|
|
aeron_term_t * term = (aeron_term_t *) userdata;
|
|
gboolean frag_found = FALSE;
|
|
wmem_list_frame_t * lf = NULL;
|
|
aeron_msg_fragment_t * frag = NULL;
|
|
|
|
if (msg->complete)
|
|
{
|
|
/* This message is complete, no need to check for orphans */
|
|
return (FALSE);
|
|
}
|
|
/* Scan through the orphan fragments */
|
|
while (TRUE)
|
|
{
|
|
lf = wmem_list_head(term->orphan_fragment);
|
|
while (lf != NULL)
|
|
{
|
|
frag = (aeron_msg_fragment_t *) wmem_list_frame_data(lf);
|
|
if (frag != NULL)
|
|
{
|
|
if (msg->next_expected_term_offset == frag->term_offset)
|
|
{
|
|
/* Found one! Remove it from the orphan list, and add it to the message */
|
|
wmem_list_remove_frame(term->orphan_fragment, lf);
|
|
aeron_msg_fragment_add(msg, frag);
|
|
frag_found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
lf = wmem_list_frame_next(lf);
|
|
}
|
|
if (!frag_found)
|
|
{
|
|
break;
|
|
}
|
|
frag_found = FALSE;
|
|
}
|
|
return (FALSE);
|
|
}
|
|
|
|
static void aeron_msg_process_orphan_fragments(aeron_term_t * term)
|
|
{
|
|
/* If we have no orphan fragments to process, nothing to do. */
|
|
if (wmem_list_count(term->orphan_fragment) == 0)
|
|
{
|
|
return;
|
|
}
|
|
wmem_tree_foreach(term->message, aeron_msg_process_orphan_fragments_msg_cb, (void *) term);
|
|
}
|
|
|
|
static aeron_msg_fragment_t * aeron_msg_fragment_create(tvbuff_t * tvb, int offset, packet_info * pinfo, aeron_packet_info_t * info)
|
|
{
|
|
aeron_msg_fragment_t * frag;
|
|
|
|
frag = wmem_new0(wmem_file_scope(), aeron_msg_fragment_t);
|
|
frag->term_offset = info->term_offset;
|
|
frag->frame_length = info->length;
|
|
frag->data_length = info->data_length;
|
|
frag->frame = pinfo->num;
|
|
frag->frame_offset = offset;
|
|
frag->data = (gchar *) tvb_memdup(wmem_file_scope(), tvb, frag->frame_offset + O_AERON_DATA_DATA, (size_t) frag->data_length);
|
|
frag->flags = info->flags;
|
|
return (frag);
|
|
}
|
|
|
|
static aeron_msg_fragment_t * aeron_msg_fragment_find(aeron_msg_t * message, aeron_packet_info_t * info)
|
|
{
|
|
aeron_msg_fragment_t * frag = NULL;
|
|
wmem_list_frame_t * lf;
|
|
|
|
if (message->next_expected_term_offset < info->term_offset)
|
|
{
|
|
return (NULL);
|
|
}
|
|
lf = wmem_list_head(message->fragment);
|
|
while (lf != NULL)
|
|
{
|
|
frag = (aeron_msg_fragment_t *) wmem_list_frame_data(lf);
|
|
if (frag != NULL)
|
|
{
|
|
if (frag->term_offset == info->term_offset)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
lf = wmem_list_frame_next(lf);
|
|
}
|
|
return (frag);
|
|
}
|
|
|
|
static aeron_msg_t * aeron_term_msg_find_le(aeron_term_t * term, guint32 term_offset)
|
|
{
|
|
/* Return the last aeron_msg_t with starting_fragment_term_offset <= offset */
|
|
aeron_msg_t * msg = (aeron_msg_t *) wmem_tree_lookup32_le(term->message, term_offset);
|
|
return (msg);
|
|
}
|
|
|
|
static aeron_msg_t * aeron_term_msg_add(aeron_term_t * term, packet_info * pinfo, aeron_packet_info_t * info)
|
|
{
|
|
aeron_msg_t * pos;
|
|
aeron_msg_t * msg;
|
|
|
|
pos = aeron_term_msg_find_le(term, info->term_offset);
|
|
if ((pos != NULL) && (pos->first_fragment_term_offset == info->term_offset))
|
|
{
|
|
return (pos);
|
|
}
|
|
msg = wmem_new0(wmem_file_scope(), aeron_msg_t);
|
|
msg->fragment = wmem_list_new(wmem_file_scope());
|
|
msg->term = term;
|
|
msg->reassembled_data = NULL;
|
|
msg->first_fragment_term_offset = info->term_offset;
|
|
msg->next_expected_term_offset = info->term_offset;
|
|
msg->length = 0;
|
|
msg->frame_length = 0;
|
|
msg->fragment_count = 0;
|
|
msg->contiguous_length = 0;
|
|
msg->begin_frame = pinfo->num;
|
|
msg->first_frame = pinfo->num;
|
|
msg->end_frame = 0;
|
|
msg->last_frame = 0;
|
|
msg->complete = FALSE;
|
|
wmem_tree_insert32(term->message, msg->first_fragment_term_offset, (void *) msg);
|
|
return (msg);
|
|
}
|
|
|
|
static void aeron_msg_process(tvbuff_t * tvb, int offset, packet_info * pinfo, aeron_transport_t * transport, aeron_packet_info_t * info, aeron_frame_info_t * finfo _U_)
|
|
{
|
|
if (aeron_reassemble_fragments && (PINFO_FD_VISITED(pinfo) == 0))
|
|
{
|
|
if ((info->flags & DATA_FLAGS_COMPLETE) != DATA_FLAGS_COMPLETE)
|
|
{
|
|
aeron_stream_t * stream = aeron_transport_stream_find(transport, info->stream_id);
|
|
if (stream != NULL)
|
|
{
|
|
aeron_term_t * term = aeron_stream_term_find(stream, info->term_id);
|
|
if (term != NULL)
|
|
{
|
|
aeron_msg_t * msg = NULL;
|
|
aeron_msg_fragment_t * frag = NULL;
|
|
|
|
if ((info->flags & DATA_FLAGS_BEGIN) == DATA_FLAGS_BEGIN)
|
|
{
|
|
/* Beginning of a message. First see if this message already exists. */
|
|
msg = aeron_term_msg_find_le(term, info->term_offset);
|
|
if (msg != NULL)
|
|
{
|
|
if (msg->first_fragment_term_offset != info->term_offset)
|
|
{
|
|
/*
|
|
A message start with a term offset:
|
|
1) Between two existing messages for this term, or
|
|
2) Less than the first message for this term
|
|
Likely this was caused by an RX or out-of-order packet. Need to create a new one.
|
|
*/
|
|
msg = NULL;
|
|
}
|
|
}
|
|
if (msg == NULL)
|
|
{
|
|
msg = aeron_term_msg_add(term, pinfo, info);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* End of message, or middle of message. See if we already have a message with a smaller starting term offset */
|
|
msg = aeron_term_msg_find_le(term, info->term_offset);
|
|
if (msg != NULL)
|
|
{
|
|
/* Is this the next expexted term offset? */
|
|
if (msg->next_expected_term_offset == info->term_offset)
|
|
{
|
|
/* Yes - we can add the fragment to the message */
|
|
}
|
|
else
|
|
{
|
|
/* Do we already have this fragment? */
|
|
frag = aeron_msg_fragment_find(msg, info);
|
|
if (frag != NULL)
|
|
{
|
|
/* Already have it, so nothing to do */
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
/* Not the next fragment, so no known message associated with it. */
|
|
msg = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* Create the fragment */
|
|
frag = aeron_msg_fragment_create(tvb, offset, pinfo, info);
|
|
if (msg == NULL)
|
|
{
|
|
/* Add the fragment to the list of orphaned fragments */
|
|
wmem_list_append(term->orphan_fragment, (void *) frag);
|
|
}
|
|
else
|
|
{
|
|
/* Add the fragment to the message */
|
|
aeron_msg_fragment_add(msg, frag);
|
|
}
|
|
/* Process the orphan list */
|
|
aeron_msg_process_orphan_fragments(term);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Aeron pad message packet dissection functions. */
|
|
/*----------------------------------------------------------------------------*/
|
|
static int dissect_aeron_pad(tvbuff_t * tvb, int offset, packet_info * pinfo, proto_tree * tree, aeron_conversation_info_t * cinfo, aeron_frame_info_t * finfo)
|
|
{
|
|
proto_tree * subtree;
|
|
proto_item * pad_item;
|
|
proto_item * channel_item;
|
|
proto_item * frame_length_item;
|
|
guint32 frame_length;
|
|
guint32 pad_length;
|
|
aeron_transport_t * transport;
|
|
guint32 session_id;
|
|
guint32 stream_id;
|
|
guint32 term_id;
|
|
guint32 term_offset;
|
|
int rounded_length;
|
|
aeron_packet_info_t pktinfo;
|
|
|
|
frame_length = tvb_get_letohl(tvb, offset + O_AERON_PAD_FRAME_LENGTH);
|
|
rounded_length = (int) aeron_pos_roundup(frame_length);
|
|
if (rounded_length < 0)
|
|
return 0;
|
|
term_offset = tvb_get_letohl(tvb, offset + O_AERON_PAD_TERM_OFFSET);
|
|
session_id = tvb_get_letohl(tvb, offset + O_AERON_PAD_SESSION_ID);
|
|
transport = aeron_transport_add(cinfo, session_id, pinfo->num);
|
|
stream_id = tvb_get_letohl(tvb, offset + O_AERON_PAD_STREAM_ID);
|
|
term_id = tvb_get_letohl(tvb, offset + O_AERON_PAD_TERM_ID);
|
|
pad_length = frame_length - L_AERON_PAD_MIN;
|
|
memset((void *) &pktinfo, 0, sizeof(aeron_packet_info_t));
|
|
pktinfo.stream_id = stream_id;
|
|
pktinfo.term_id = term_id;
|
|
pktinfo.term_offset = term_offset;
|
|
pktinfo.info_flags = AERON_PACKET_INFO_FLAGS_STREAM_ID_VALID | AERON_PACKET_INFO_FLAGS_TERM_ID_VALID | AERON_PACKET_INFO_FLAGS_TERM_OFFSET_VALID;
|
|
pktinfo.length = frame_length;
|
|
pktinfo.data_length = pad_length;
|
|
pktinfo.type = HDR_TYPE_PAD;
|
|
pktinfo.flags = tvb_get_guint8(tvb, offset + O_AERON_PAD_FLAGS);
|
|
if (aeron_frame_info_setup(pinfo, transport, &pktinfo, finfo) < 0)
|
|
return 0;
|
|
|
|
aeron_info_stream_progress_report(pinfo, HDR_TYPE_PAD, pktinfo.flags, term_id, term_offset, finfo);
|
|
pad_item = proto_tree_add_none_format(tree, hf_aeron_pad, tvb, offset, -1, "Pad Frame: Term 0x%x, Ofs %" G_GUINT32_FORMAT ", Len %" G_GUINT32_FORMAT "(%d)",
|
|
term_id, term_offset, frame_length, rounded_length);
|
|
subtree = proto_item_add_subtree(pad_item, ett_aeron_pad);
|
|
channel_item = proto_tree_add_uint64(subtree, hf_aeron_channel_id, tvb, 0, 0, transport->channel_id);
|
|
PROTO_ITEM_SET_GENERATED(channel_item);
|
|
frame_length_item = proto_tree_add_item(subtree, hf_aeron_pad_frame_length, tvb, offset + O_AERON_PAD_FRAME_LENGTH, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_pad_version, tvb, offset + O_AERON_PAD_VERSION, 1, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_pad_flags, tvb, offset + O_AERON_PAD_FLAGS, 1, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_pad_type, tvb, offset + O_AERON_PAD_TYPE, 2, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_pad_term_offset, tvb, offset + O_AERON_PAD_TERM_OFFSET, 4, ENC_LITTLE_ENDIAN);
|
|
aeron_next_offset_report(tvb, subtree, transport, stream_id, term_id, term_offset, (guint32) rounded_length);
|
|
proto_tree_add_item(subtree, hf_aeron_pad_session_id, tvb, offset + O_AERON_PAD_SESSION_ID, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_pad_stream_id, tvb, offset + O_AERON_PAD_STREAM_ID, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_pad_term_id, tvb, offset + O_AERON_PAD_TERM_ID, 4, ENC_LITTLE_ENDIAN);
|
|
aeron_sequence_report(tvb, pinfo, subtree, transport, &pktinfo, finfo);
|
|
aeron_stream_report(tvb, pinfo, subtree, transport, finfo);
|
|
proto_item_set_len(pad_item, rounded_length);
|
|
if (frame_length < L_AERON_PAD_MIN)
|
|
{
|
|
expert_add_info(pinfo, frame_length_item, &ei_aeron_analysis_invalid_pad_length);
|
|
return (-rounded_length);
|
|
}
|
|
return (rounded_length);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Aeron data message packet dissection functions. */
|
|
/*----------------------------------------------------------------------------*/
|
|
static void dissect_aeron_reassembled_data(packet_info * pinfo, proto_tree * tree, aeron_frame_info_t * finfo)
|
|
{
|
|
proto_item * frag_item;
|
|
proto_tree * frag_tree;
|
|
aeron_msg_t * msg;
|
|
wmem_list_frame_t * lf;
|
|
gboolean first_item = TRUE;
|
|
guint32 msg_ofs = 0;
|
|
|
|
if (finfo->message == NULL)
|
|
{
|
|
return;
|
|
}
|
|
msg = finfo->message;
|
|
add_new_data_source(pinfo, msg->reassembled_data, "Reassembled Data");
|
|
frag_item = proto_tree_add_none_format(tree,
|
|
hf_aeron_data_reassembly,
|
|
msg->reassembled_data,
|
|
0,
|
|
tvb_reported_length_remaining(msg->reassembled_data, 0),
|
|
"%" G_GUINT32_FORMAT " Reassembled Fragments (%" G_GUINT32_FORMAT " bytes):",
|
|
msg->fragment_count,
|
|
msg->length);
|
|
frag_tree = proto_item_add_subtree(frag_item, ett_aeron_data_reassembly);
|
|
lf = wmem_list_head(msg->fragment);
|
|
while (lf != NULL)
|
|
{
|
|
aeron_msg_fragment_t * frag = (aeron_msg_fragment_t *) wmem_list_frame_data(lf);
|
|
if (frag != NULL)
|
|
{
|
|
proto_item * pi;
|
|
pi = proto_tree_add_uint_format_value(frag_tree,
|
|
hf_aeron_data_reassembly_fragment,
|
|
msg->reassembled_data,
|
|
msg_ofs,
|
|
frag->data_length,
|
|
frag->frame,
|
|
"Frame: %" G_GUINT32_FORMAT ", payload: %" G_GUINT32_FORMAT "-%" G_GUINT32_FORMAT " (%" G_GUINT32_FORMAT " bytes)",
|
|
frag->frame,
|
|
msg_ofs,
|
|
(msg_ofs + frag->data_length) - 1,
|
|
frag->data_length);
|
|
PROTO_ITEM_SET_GENERATED(pi);
|
|
if (first_item)
|
|
{
|
|
proto_item_append_text(frag_item, " #%" G_GUINT32_FORMAT "(%" G_GUINT32_FORMAT ")", frag->frame, frag->data_length);
|
|
}
|
|
else
|
|
{
|
|
proto_item_append_text(frag_item, ", #%" G_GUINT32_FORMAT "(%" G_GUINT32_FORMAT ")", frag->frame, frag->data_length);
|
|
}
|
|
msg_ofs += frag->data_length;
|
|
first_item = FALSE;
|
|
}
|
|
lf = wmem_list_frame_next(lf);
|
|
}
|
|
PROTO_ITEM_SET_GENERATED(frag_item);
|
|
}
|
|
|
|
static int dissect_aeron_data(tvbuff_t * tvb, int offset, packet_info * pinfo, proto_tree * tree, aeron_conversation_info_t * cinfo, aeron_frame_info_t * finfo)
|
|
{
|
|
proto_tree * subtree;
|
|
proto_item * data_item;
|
|
proto_item * channel_item;
|
|
proto_item * frame_length_item;
|
|
guint32 frame_length;
|
|
static const int * flags[] =
|
|
{
|
|
&hf_aeron_data_flags_b,
|
|
&hf_aeron_data_flags_e,
|
|
NULL
|
|
};
|
|
aeron_transport_t * transport;
|
|
guint32 session_id;
|
|
guint32 stream_id;
|
|
guint32 term_id;
|
|
guint32 term_offset;
|
|
guint32 data_length;
|
|
int rounded_length = 0;
|
|
aeron_packet_info_t pktinfo;
|
|
guint32 offset_increment = 0;
|
|
|
|
frame_length = tvb_get_letohl(tvb, offset + O_AERON_DATA_FRAME_LENGTH);
|
|
if (frame_length == 0)
|
|
{
|
|
rounded_length = O_AERON_DATA_DATA;
|
|
data_length = 0;
|
|
offset_increment = 0;
|
|
}
|
|
else
|
|
{
|
|
offset_increment = aeron_pos_roundup(frame_length);
|
|
rounded_length = (int) offset_increment;
|
|
if (rounded_length < 0)
|
|
return 0;
|
|
data_length = frame_length - O_AERON_DATA_DATA;
|
|
}
|
|
term_offset = tvb_get_letohl(tvb, offset + O_AERON_DATA_TERM_OFFSET);
|
|
session_id = tvb_get_letohl(tvb, offset + O_AERON_DATA_SESSION_ID);
|
|
transport = aeron_transport_add(cinfo, session_id, pinfo->num);
|
|
stream_id = tvb_get_letohl(tvb, offset + O_AERON_DATA_STREAM_ID);
|
|
term_id = tvb_get_letohl(tvb, offset + O_AERON_DATA_TERM_ID);
|
|
memset((void *) &pktinfo, 0, sizeof(aeron_packet_info_t));
|
|
pktinfo.stream_id = stream_id;
|
|
pktinfo.term_id = term_id;
|
|
pktinfo.term_offset = term_offset;
|
|
pktinfo.info_flags = AERON_PACKET_INFO_FLAGS_STREAM_ID_VALID | AERON_PACKET_INFO_FLAGS_TERM_ID_VALID | AERON_PACKET_INFO_FLAGS_TERM_OFFSET_VALID;
|
|
pktinfo.length = frame_length;
|
|
pktinfo.data_length = data_length;
|
|
pktinfo.type = HDR_TYPE_DATA;
|
|
pktinfo.flags = tvb_get_guint8(tvb, offset + O_AERON_DATA_FLAGS);
|
|
if (aeron_frame_info_setup(pinfo, transport, &pktinfo, finfo) < 0)
|
|
return 0;
|
|
|
|
aeron_info_stream_progress_report(pinfo, HDR_TYPE_DATA, pktinfo.flags, term_id, term_offset, finfo);
|
|
data_item = proto_tree_add_none_format(tree, hf_aeron_data, tvb, offset, -1, "Data Frame: Term 0x%x, Ofs %" G_GUINT32_FORMAT ", Len %" G_GUINT32_FORMAT "(%d)",
|
|
(guint32) term_id, term_offset, frame_length, rounded_length);
|
|
subtree = proto_item_add_subtree(data_item, ett_aeron_data);
|
|
channel_item = proto_tree_add_uint64(subtree, hf_aeron_channel_id, tvb, 0, 0, transport->channel_id);
|
|
PROTO_ITEM_SET_GENERATED(channel_item);
|
|
frame_length_item = proto_tree_add_item(subtree, hf_aeron_data_frame_length, tvb, offset + O_AERON_DATA_FRAME_LENGTH, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_data_version, tvb, offset + O_AERON_DATA_VERSION, 1, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_bitmask(subtree, tvb, offset + O_AERON_DATA_FLAGS, hf_aeron_data_flags, ett_aeron_data_flags, flags, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_data_type, tvb, offset + O_AERON_DATA_TYPE, 2, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_data_term_offset, tvb, offset + O_AERON_DATA_TERM_OFFSET, 4, ENC_LITTLE_ENDIAN);
|
|
aeron_next_offset_report(tvb, subtree, transport, stream_id, term_id, term_offset, offset_increment);
|
|
proto_tree_add_item(subtree, hf_aeron_data_session_id, tvb, offset + O_AERON_DATA_SESSION_ID, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_data_stream_id, tvb, offset + O_AERON_DATA_STREAM_ID, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_data_term_id, tvb, offset + O_AERON_DATA_TERM_ID, 4, ENC_LITTLE_ENDIAN);
|
|
if (data_length > 0)
|
|
{
|
|
tvbuff_t * data_tvb = NULL;
|
|
gboolean can_call_subdissector = FALSE;
|
|
gboolean dissector_found = FALSE;
|
|
heur_dtbl_entry_t * hdtbl_entry;
|
|
|
|
aeron_msg_process(tvb, offset, pinfo, transport, &pktinfo, finfo);
|
|
if ((pktinfo.flags & DATA_FLAGS_COMPLETE) == DATA_FLAGS_COMPLETE)
|
|
{
|
|
can_call_subdissector = TRUE;
|
|
}
|
|
if (finfo != NULL)
|
|
{
|
|
if ((finfo->flags & AERON_FRAME_INFO_FLAGS_REASSEMBLED_MSG) != 0)
|
|
{
|
|
dissect_aeron_reassembled_data(pinfo, subtree, finfo);
|
|
data_tvb = finfo->message->reassembled_data;
|
|
can_call_subdissector = TRUE;
|
|
}
|
|
else
|
|
{
|
|
data_tvb = tvb_new_subset_length(tvb, offset + O_AERON_DATA_DATA, data_length);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
data_tvb = tvb_new_subset_length(tvb, offset + O_AERON_DATA_DATA, data_length);
|
|
}
|
|
if (can_call_subdissector && aeron_use_heuristic_subdissectors)
|
|
{
|
|
dissector_found = dissector_try_heuristic(aeron_heuristic_subdissector_list, data_tvb, pinfo, subtree, &hdtbl_entry, NULL);
|
|
}
|
|
if (!dissector_found)
|
|
{
|
|
call_data_dissector(data_tvb, pinfo, subtree);
|
|
}
|
|
}
|
|
aeron_sequence_report(tvb, pinfo, subtree, transport, &pktinfo, finfo);
|
|
aeron_stream_report(tvb, pinfo, subtree, transport, finfo);
|
|
proto_item_set_len(data_item, rounded_length);
|
|
if ((frame_length != 0) && (frame_length < L_AERON_DATA_MIN))
|
|
{
|
|
expert_add_info(pinfo, frame_length_item, &ei_aeron_analysis_invalid_data_length);
|
|
return (-rounded_length);
|
|
}
|
|
return (rounded_length);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Aeron NAK packet dissection functions. */
|
|
/*----------------------------------------------------------------------------*/
|
|
static int dissect_aeron_nak(tvbuff_t * tvb, int offset, packet_info * pinfo, proto_tree * tree, aeron_conversation_info_t * cinfo, aeron_frame_info_t * finfo)
|
|
{
|
|
proto_tree * subtree;
|
|
proto_item * nak_item;
|
|
proto_item * frame_length_item;
|
|
proto_item * channel_item;
|
|
proto_item * nak_offset_item;
|
|
guint32 frame_length;
|
|
aeron_transport_t * transport;
|
|
guint32 session_id;
|
|
guint32 stream_id;
|
|
guint32 term_id;
|
|
guint32 nak_term_offset;
|
|
guint32 nak_length;
|
|
int rounded_length;
|
|
aeron_packet_info_t pktinfo;
|
|
|
|
frame_length = tvb_get_letohl(tvb, offset + O_AERON_NAK_FRAME_LENGTH);
|
|
rounded_length = (int) aeron_pos_roundup(frame_length);
|
|
if (rounded_length < 0)
|
|
return 0;
|
|
session_id = tvb_get_letohl(tvb, offset + O_AERON_NAK_SESSION_ID);
|
|
transport = aeron_transport_add(cinfo, session_id, pinfo->num);
|
|
stream_id = tvb_get_letohl(tvb, offset + O_AERON_NAK_STREAM_ID);
|
|
term_id = tvb_get_letohl(tvb, offset + O_AERON_NAK_TERM_ID);
|
|
nak_term_offset = tvb_get_letohl(tvb, offset + O_AERON_NAK_TERM_OFFSET);
|
|
nak_length = tvb_get_letohl(tvb, offset + O_AERON_NAK_LENGTH);
|
|
memset((void *) &pktinfo, 0, sizeof(aeron_packet_info_t));
|
|
pktinfo.stream_id = stream_id;
|
|
pktinfo.term_id = term_id;
|
|
pktinfo.info_flags = AERON_PACKET_INFO_FLAGS_STREAM_ID_VALID | AERON_PACKET_INFO_FLAGS_TERM_ID_VALID;
|
|
pktinfo.nak_term_offset = nak_term_offset;
|
|
pktinfo.nak_length = nak_length;
|
|
pktinfo.type = HDR_TYPE_NAK;
|
|
pktinfo.flags = tvb_get_guint8(tvb, offset + O_AERON_NAK_FLAGS);
|
|
if (aeron_frame_info_setup(pinfo, transport, &pktinfo, finfo) < 0)
|
|
return 0;
|
|
|
|
col_append_sep_str(pinfo->cinfo, COL_INFO, ", ", "NAK");
|
|
nak_item = proto_tree_add_none_format(tree, hf_aeron_nak, tvb, offset, -1, "NAK Frame: Term 0x%x, Ofs %" G_GUINT32_FORMAT ", Len %" G_GUINT32_FORMAT,
|
|
term_id, nak_term_offset, nak_length);
|
|
subtree = proto_item_add_subtree(nak_item, ett_aeron_nak);
|
|
channel_item = proto_tree_add_uint64(subtree, hf_aeron_channel_id, tvb, 0, 0, transport->channel_id);
|
|
PROTO_ITEM_SET_GENERATED(channel_item);
|
|
frame_length_item = proto_tree_add_item(subtree, hf_aeron_nak_frame_length, tvb, offset + O_AERON_NAK_FRAME_LENGTH, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_nak_version, tvb, offset + O_AERON_NAK_VERSION, 1, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_nak_flags, tvb, offset + O_AERON_NAK_FLAGS, 1, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_nak_type, tvb, offset + O_AERON_NAK_TYPE, 2, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_nak_session_id, tvb, offset + O_AERON_NAK_SESSION_ID, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_nak_stream_id, tvb, offset + O_AERON_NAK_STREAM_ID, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_nak_term_id, tvb, offset + O_AERON_NAK_TERM_ID, 4, ENC_LITTLE_ENDIAN);
|
|
nak_offset_item = proto_tree_add_item(subtree, hf_aeron_nak_term_offset, tvb, offset + O_AERON_NAK_TERM_OFFSET, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_nak_length, tvb, offset + O_AERON_NAK_LENGTH, 4, ENC_LITTLE_ENDIAN);
|
|
expert_add_info_format(pinfo, nak_offset_item, &ei_aeron_analysis_nak, "NAK offset %" G_GUINT32_FORMAT " length %" G_GUINT32_FORMAT, nak_term_offset, nak_length);
|
|
aeron_sequence_report(tvb, pinfo, subtree, transport, &pktinfo, finfo);
|
|
proto_item_set_len(nak_item, rounded_length);
|
|
if (frame_length != L_AERON_NAK)
|
|
{
|
|
expert_add_info(pinfo, frame_length_item, &ei_aeron_analysis_invalid_nak_length);
|
|
return (-rounded_length);
|
|
}
|
|
return (rounded_length);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Aeron status message packet dissection functions. */
|
|
/*----------------------------------------------------------------------------*/
|
|
static void aeron_window_resize_report(packet_info * pinfo, proto_item * item, aeron_frame_info_t * finfo)
|
|
{
|
|
if (aeron_sequence_analysis && aeron_stream_analysis && (finfo != NULL) && (finfo->stream_analysis != NULL))
|
|
{
|
|
if ((finfo->stream_analysis->flags & AERON_STREAM_ANALYSIS_FLAGS_WINDOW_RESIZE) != 0)
|
|
{
|
|
expert_add_info(pinfo, item, &ei_aeron_analysis_window_resize);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int dissect_aeron_sm(tvbuff_t * tvb, int offset, packet_info * pinfo, proto_tree * tree, aeron_conversation_info_t * cinfo, aeron_frame_info_t * finfo)
|
|
{
|
|
proto_tree * subtree;
|
|
proto_item * sm_item;
|
|
proto_item * frame_length_item;
|
|
proto_item * item;
|
|
proto_item * rcv_window_item;
|
|
guint32 frame_length;
|
|
static const int * flags[] =
|
|
{
|
|
&hf_aeron_sm_flags_s,
|
|
NULL
|
|
};
|
|
guint32 feedback_length;
|
|
aeron_transport_t * transport;
|
|
guint32 session_id;
|
|
guint32 stream_id;
|
|
guint32 term_id;
|
|
guint32 consumption_offset;
|
|
guint32 rcv_window;
|
|
int rounded_length;
|
|
aeron_packet_info_t pktinfo;
|
|
|
|
frame_length = tvb_get_letohl(tvb, offset + O_AERON_SM_FRAME_LENGTH);
|
|
feedback_length = frame_length - O_AERON_SM_FEEDBACK;
|
|
rounded_length = (int) aeron_pos_roundup(frame_length);
|
|
if (rounded_length < 0)
|
|
return 0;
|
|
session_id = tvb_get_letohl(tvb, offset + O_AERON_SM_SESSION_ID);
|
|
transport = aeron_transport_add(cinfo, session_id, pinfo->num);
|
|
stream_id = tvb_get_letohl(tvb, offset + O_AERON_SM_STREAM_ID);
|
|
term_id = tvb_get_letohl(tvb, offset + O_AERON_SM_TERM_ID);
|
|
consumption_offset = tvb_get_letohl(tvb, offset + O_AERON_SM_COMPLETED_TERM_OFFSET);
|
|
rcv_window = tvb_get_letohl(tvb, offset + O_AERON_SM_RECEIVER_WINDOW);
|
|
memset((void *) &pktinfo, 0, sizeof(aeron_packet_info_t));
|
|
pktinfo.stream_id = stream_id;
|
|
pktinfo.info_flags = AERON_PACKET_INFO_FLAGS_STREAM_ID_VALID;
|
|
pktinfo.flags = tvb_get_guint8(tvb, offset + O_AERON_SM_FLAGS);
|
|
if ((pktinfo.flags & STATUS_FLAGS_SETUP) == 0)
|
|
{
|
|
pktinfo.term_id = term_id;
|
|
pktinfo.term_offset = consumption_offset;
|
|
pktinfo.info_flags |= (AERON_PACKET_INFO_FLAGS_TERM_ID_VALID | AERON_PACKET_INFO_FLAGS_TERM_OFFSET_VALID);
|
|
pktinfo.receiver_window = rcv_window;
|
|
}
|
|
else
|
|
{
|
|
pktinfo.term_id = 0;
|
|
pktinfo.term_offset = 0;
|
|
pktinfo.receiver_window = 0;
|
|
}
|
|
pktinfo.length = 0;
|
|
pktinfo.data_length = 0;
|
|
pktinfo.type = HDR_TYPE_SM;
|
|
if (aeron_frame_info_setup(pinfo, transport, &pktinfo, finfo) < 0)
|
|
return 0;
|
|
|
|
aeron_info_stream_progress_report(pinfo, HDR_TYPE_SM, pktinfo.flags, term_id, consumption_offset, finfo);
|
|
sm_item = proto_tree_add_none_format(tree, hf_aeron_sm, tvb, offset, -1, "Status Message: Term 0x%x, ConsumptionOfs %" G_GUINT32_FORMAT ", RcvWindow %" G_GUINT32_FORMAT,
|
|
term_id, consumption_offset, rcv_window);
|
|
subtree = proto_item_add_subtree(sm_item, ett_aeron_sm);
|
|
item = proto_tree_add_uint64(subtree, hf_aeron_channel_id, tvb, 0, 0, transport->channel_id);
|
|
PROTO_ITEM_SET_GENERATED(item);
|
|
frame_length_item = proto_tree_add_item(subtree, hf_aeron_sm_frame_length, tvb, offset + O_AERON_SM_FRAME_LENGTH, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_sm_version, tvb, offset + O_AERON_SM_VERSION, 1, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_bitmask(subtree, tvb, offset + O_AERON_SM_FLAGS, hf_aeron_sm_flags, ett_aeron_sm_flags, flags, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_sm_type, tvb, offset + O_AERON_SM_TYPE, 2, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_sm_session_id, tvb, offset + O_AERON_SM_SESSION_ID, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_sm_stream_id, tvb, offset + O_AERON_SM_STREAM_ID, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_sm_consumption_term_id, tvb, offset + O_AERON_SM_TERM_ID, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_sm_consumption_term_offset, tvb, offset + O_AERON_SM_COMPLETED_TERM_OFFSET, 4, ENC_LITTLE_ENDIAN);
|
|
rcv_window_item = proto_tree_add_item(subtree, hf_aeron_sm_receiver_window, tvb, offset + O_AERON_SM_RECEIVER_WINDOW, 4, ENC_LITTLE_ENDIAN);
|
|
aeron_window_resize_report(pinfo, rcv_window_item, finfo);
|
|
if (feedback_length > 0)
|
|
{
|
|
proto_tree_add_item(subtree, hf_aeron_sm_feedback, tvb, offset + O_AERON_SM_FEEDBACK, feedback_length, ENC_NA);
|
|
}
|
|
aeron_sequence_report(tvb, pinfo, subtree, transport, &pktinfo, finfo);
|
|
aeron_stream_report(tvb, pinfo, subtree, transport, finfo);
|
|
proto_item_set_len(sm_item, rounded_length);
|
|
if (frame_length < L_AERON_SM_MIN)
|
|
{
|
|
expert_add_info(pinfo, frame_length_item, &ei_aeron_analysis_invalid_sm_length);
|
|
return (-rounded_length);
|
|
}
|
|
return (rounded_length);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Aeron error packet dissection functions. */
|
|
/*----------------------------------------------------------------------------*/
|
|
static int dissect_aeron_err(tvbuff_t * tvb, int offset, packet_info * pinfo, proto_tree * tree)
|
|
{
|
|
proto_tree * subtree;
|
|
proto_item * err_item;
|
|
proto_item * frame_length_item;
|
|
int rounded_length;
|
|
guint32 bad_frame_length;
|
|
gint string_length;
|
|
guint32 frame_length;
|
|
int ofs;
|
|
|
|
frame_length = tvb_get_letohl(tvb, offset + O_AERON_ERR_FRAME_LENGTH);
|
|
col_append_sep_str(pinfo->cinfo, COL_INFO, ", ", "Error");
|
|
err_item = proto_tree_add_item(tree, hf_aeron_err, tvb, offset, -1, ENC_NA);
|
|
subtree = proto_item_add_subtree(err_item, ett_aeron_err);
|
|
frame_length_item = proto_tree_add_item(subtree, hf_aeron_err_frame_length, tvb, offset + O_AERON_ERR_FRAME_LENGTH, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_err_version, tvb, offset + O_AERON_ERR_VERSION, 1, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_err_code, tvb, offset + O_AERON_ERR_CODE, 1, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_err_type, tvb, offset + O_AERON_ERR_TYPE, 2, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_err_off_frame_length, tvb, offset + O_AERON_ERR_OFFENDING_FRAME_LENGTH, 4, ENC_LITTLE_ENDIAN);
|
|
bad_frame_length = tvb_get_letohl(tvb, offset + O_AERON_ERR_OFFENDING_FRAME_LENGTH);
|
|
ofs = offset + O_AERON_ERR_OFFENDING_HEADER;
|
|
proto_tree_add_item(subtree, hf_aeron_err_off_hdr, tvb, offset + ofs, bad_frame_length, ENC_NA);
|
|
ofs += bad_frame_length;
|
|
string_length = frame_length - ofs;
|
|
if (string_length > 0)
|
|
{
|
|
proto_tree_add_item(subtree, hf_aeron_err_string, tvb, offset + ofs, string_length, ENC_ASCII|ENC_NA);
|
|
}
|
|
rounded_length = (int) aeron_pos_roundup(frame_length);
|
|
if (rounded_length < 0)
|
|
return 0;
|
|
proto_item_set_len(err_item, rounded_length);
|
|
if (frame_length < L_AERON_ERR_MIN)
|
|
{
|
|
expert_add_info(pinfo, frame_length_item, &ei_aeron_analysis_invalid_err_length);
|
|
return (-rounded_length);
|
|
}
|
|
return (rounded_length);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Aeron setup packet dissection functions. */
|
|
/*----------------------------------------------------------------------------*/
|
|
static void aeron_set_stream_mtu_term_length(packet_info * pinfo, aeron_transport_t * transport, guint32 stream_id, guint32 mtu, guint32 term_length)
|
|
{
|
|
if (PINFO_FD_VISITED(pinfo) == 0)
|
|
{
|
|
aeron_stream_t * stream = aeron_transport_stream_find(transport, stream_id);
|
|
if (stream != NULL)
|
|
{
|
|
stream->term_length = term_length;
|
|
stream->mtu = mtu;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int dissect_aeron_setup(tvbuff_t * tvb, int offset, packet_info * pinfo, proto_tree * tree, aeron_conversation_info_t * cinfo, aeron_frame_info_t * finfo)
|
|
{
|
|
proto_tree * subtree;
|
|
proto_item * setup_item;
|
|
proto_item * frame_length_item;
|
|
guint32 frame_length;
|
|
proto_item * channel_item;
|
|
aeron_transport_t * transport;
|
|
guint32 session_id;
|
|
guint32 stream_id;
|
|
guint32 active_term_id;
|
|
guint32 initial_term_id;
|
|
guint32 term_offset;
|
|
guint32 term_length;
|
|
guint32 mtu;
|
|
int rounded_length;
|
|
aeron_packet_info_t pktinfo;
|
|
|
|
frame_length = tvb_get_letohl(tvb, offset + O_AERON_SETUP_FRAME_LENGTH);
|
|
rounded_length = (int) aeron_pos_roundup(frame_length);
|
|
if (rounded_length < 0)
|
|
return 0;
|
|
term_offset = tvb_get_letohl(tvb, offset + O_AERON_SETUP_TERM_OFFSET);
|
|
session_id = tvb_get_letohl(tvb, offset + O_AERON_SETUP_SESSION_ID);
|
|
transport = aeron_transport_add(cinfo, session_id, pinfo->num);
|
|
stream_id = tvb_get_letohl(tvb, offset + O_AERON_SETUP_STREAM_ID);
|
|
initial_term_id = tvb_get_letohl(tvb, offset + O_AERON_SETUP_INITIAL_TERM_ID);
|
|
active_term_id = tvb_get_letohl(tvb, offset + O_AERON_SETUP_ACTIVE_TERM_ID);
|
|
memset((void *) &pktinfo, 0, sizeof(aeron_packet_info_t));
|
|
pktinfo.stream_id = stream_id;
|
|
pktinfo.term_id = active_term_id;
|
|
pktinfo.term_offset = 0;
|
|
pktinfo.info_flags = AERON_PACKET_INFO_FLAGS_STREAM_ID_VALID | AERON_PACKET_INFO_FLAGS_TERM_ID_VALID;
|
|
pktinfo.length = 0;
|
|
pktinfo.data_length = 0;
|
|
pktinfo.receiver_window = 0;
|
|
pktinfo.type = HDR_TYPE_SETUP;
|
|
pktinfo.flags = 0;
|
|
if (aeron_frame_info_setup(pinfo, transport, &pktinfo, finfo) < 0)
|
|
return 0;
|
|
term_length = tvb_get_letohl(tvb, offset + O_AERON_SETUP_TERM_LENGTH);
|
|
mtu = tvb_get_letohl(tvb, offset + O_AERON_SETUP_MTU);
|
|
aeron_set_stream_mtu_term_length(pinfo, transport, stream_id, mtu, term_length);
|
|
|
|
col_append_sep_str(pinfo->cinfo, COL_INFO, ", ", "Setup");
|
|
setup_item = proto_tree_add_none_format(tree, hf_aeron_setup, tvb, offset, -1, "Setup Frame: InitTerm 0x%x, ActiveTerm 0x%x, TermLen %" G_GUINT32_FORMAT ", Ofs %" G_GUINT32_FORMAT ", MTU %" G_GUINT32_FORMAT,
|
|
initial_term_id, (guint32) active_term_id, term_length, term_offset, mtu);
|
|
subtree = proto_item_add_subtree(setup_item, ett_aeron_setup);
|
|
channel_item = proto_tree_add_uint64(subtree, hf_aeron_channel_id, tvb, 0, 0, transport->channel_id);
|
|
PROTO_ITEM_SET_GENERATED(channel_item);
|
|
frame_length_item = proto_tree_add_item(subtree, hf_aeron_setup_frame_length, tvb, offset + O_AERON_SETUP_FRAME_LENGTH, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_setup_version, tvb, offset + O_AERON_SETUP_VERSION, 1, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_setup_flags, tvb, offset + O_AERON_SETUP_FLAGS, 1, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_setup_type, tvb, offset + O_AERON_SETUP_TYPE, 2, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_setup_term_offset, tvb, offset + O_AERON_SETUP_TERM_OFFSET, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_setup_session_id, tvb, offset + O_AERON_SETUP_SESSION_ID, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_setup_stream_id, tvb, offset + O_AERON_SETUP_STREAM_ID, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_setup_initial_term_id, tvb, offset + O_AERON_SETUP_INITIAL_TERM_ID, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_setup_active_term_id, tvb, offset + O_AERON_SETUP_ACTIVE_TERM_ID, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_setup_term_length, tvb, offset + O_AERON_SETUP_TERM_LENGTH, 4, ENC_LITTLE_ENDIAN);
|
|
proto_tree_add_item(subtree, hf_aeron_setup_mtu, tvb, offset + O_AERON_SETUP_MTU, 4, ENC_LITTLE_ENDIAN);
|
|
aeron_sequence_report(tvb, pinfo, subtree, transport, &pktinfo, finfo);
|
|
proto_item_set_len(setup_item, rounded_length);
|
|
if (frame_length != L_AERON_SETUP)
|
|
{
|
|
expert_add_info(pinfo, frame_length_item, &ei_aeron_analysis_invalid_setup_length);
|
|
return (-rounded_length);
|
|
}
|
|
return (rounded_length);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Aeron packet dissector. */
|
|
/*----------------------------------------------------------------------------*/
|
|
static int dissect_aeron(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, void * user_data _U_)
|
|
{
|
|
int total_dissected_length = 0;
|
|
guint16 frame_type;
|
|
proto_tree * aeron_tree;
|
|
proto_item * aeron_item;
|
|
int dissected_length = 0;
|
|
int offset = 0;
|
|
int length_remaining;
|
|
aeron_conversation_info_t * cinfo;
|
|
|
|
/* Get enough information to determine the conversation info.
|
|
Make sure that we don't throw an exception before we know that
|
|
this packet contains our protocol. */
|
|
if (tvb_captured_length_remaining(tvb, offset) < 2)
|
|
return 0;
|
|
frame_type = tvb_get_letohs(tvb, offset + O_AERON_BASIC_TYPE);
|
|
cinfo = aeron_setup_conversation_info(pinfo, frame_type);
|
|
if (!cinfo)
|
|
return 0;
|
|
|
|
col_add_str(pinfo->cinfo, COL_PROTOCOL, "Aeron");
|
|
col_clear(pinfo->cinfo, COL_INFO);
|
|
col_add_str(pinfo->cinfo, COL_INFO, aeron_format_transport_uri(cinfo));
|
|
col_set_fence(pinfo->cinfo, COL_INFO);
|
|
|
|
length_remaining = tvb_reported_length(tvb);
|
|
aeron_item = proto_tree_add_protocol_format(tree, proto_aeron, tvb, offset, -1, "Aeron Protocol");
|
|
aeron_tree = proto_item_add_subtree(aeron_item, ett_aeron);
|
|
while (length_remaining > 0)
|
|
{
|
|
aeron_frame_info_t * finfo = NULL;
|
|
|
|
if (aeron_sequence_analysis)
|
|
{
|
|
finfo = aeron_frame_info_add(pinfo->num, (guint32) offset);
|
|
}
|
|
frame_type = tvb_get_letohs(tvb, offset + O_AERON_BASIC_TYPE);
|
|
cinfo = aeron_setup_conversation_info(pinfo, frame_type);
|
|
switch (frame_type)
|
|
{
|
|
case HDR_TYPE_PAD:
|
|
dissected_length = dissect_aeron_pad(tvb, offset, pinfo, aeron_tree, cinfo, finfo);
|
|
break;
|
|
case HDR_TYPE_DATA:
|
|
dissected_length = dissect_aeron_data(tvb, offset, pinfo, aeron_tree, cinfo, finfo);
|
|
break;
|
|
case HDR_TYPE_NAK:
|
|
dissected_length = dissect_aeron_nak(tvb, offset, pinfo, aeron_tree, cinfo, finfo);
|
|
break;
|
|
case HDR_TYPE_SM:
|
|
dissected_length = dissect_aeron_sm(tvb, offset, pinfo, aeron_tree, cinfo, finfo);
|
|
break;
|
|
case HDR_TYPE_ERR:
|
|
dissected_length = dissect_aeron_err(tvb, offset, pinfo, aeron_tree);
|
|
break;
|
|
case HDR_TYPE_SETUP:
|
|
dissected_length = dissect_aeron_setup(tvb, offset, pinfo, aeron_tree, cinfo, finfo);
|
|
break;
|
|
case HDR_TYPE_EXT:
|
|
default:
|
|
return (total_dissected_length);
|
|
}
|
|
if (dissected_length <= 0)
|
|
{
|
|
total_dissected_length += -dissected_length;
|
|
proto_item_set_len(aeron_item, total_dissected_length);
|
|
return (total_dissected_length);
|
|
}
|
|
total_dissected_length += dissected_length;
|
|
offset += dissected_length;
|
|
length_remaining -= dissected_length;
|
|
proto_item_set_len(aeron_item, total_dissected_length);
|
|
}
|
|
return (total_dissected_length);
|
|
}
|
|
|
|
static gboolean test_aeron_packet(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, void * user_data)
|
|
{
|
|
guint8 ver;
|
|
guint16 packet_type;
|
|
gint length;
|
|
gint length_remaining;
|
|
int rc;
|
|
|
|
length_remaining = tvb_captured_length_remaining(tvb, 0);
|
|
if (length_remaining < HDR_LENGTH_MIN)
|
|
{
|
|
return (FALSE);
|
|
}
|
|
/* We know we have at least HDR_LENGTH_MIN (12) bytes captured */
|
|
ver = tvb_get_guint8(tvb, O_AERON_BASIC_VERSION);
|
|
if (ver != 0)
|
|
{
|
|
return (FALSE);
|
|
}
|
|
packet_type = tvb_get_letohs(tvb, O_AERON_BASIC_TYPE);
|
|
switch (packet_type)
|
|
{
|
|
case HDR_TYPE_PAD:
|
|
case HDR_TYPE_DATA:
|
|
case HDR_TYPE_NAK:
|
|
case HDR_TYPE_SM:
|
|
case HDR_TYPE_ERR:
|
|
case HDR_TYPE_SETUP:
|
|
case HDR_TYPE_EXT:
|
|
break;
|
|
default:
|
|
return (FALSE);
|
|
}
|
|
length = (gint) (tvb_get_letohl(tvb, O_AERON_BASIC_FRAME_LENGTH) & 0x7fffffff);
|
|
if (!((packet_type == HDR_TYPE_DATA) && (length == 0)))
|
|
{
|
|
if (length < HDR_LENGTH_MIN)
|
|
{
|
|
return (FALSE);
|
|
}
|
|
}
|
|
if (packet_type == HDR_TYPE_PAD)
|
|
{
|
|
/* Pad frames can't have a zero term offset */
|
|
guint32 term_offset = tvb_get_letohl(tvb, O_AERON_PAD_TERM_OFFSET);
|
|
if (term_offset == 0)
|
|
{
|
|
return (FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (length > length_remaining)
|
|
{
|
|
return (FALSE);
|
|
}
|
|
}
|
|
rc = dissect_aeron(tvb, pinfo, tree, user_data);
|
|
if (rc == 0)
|
|
{
|
|
return (FALSE);
|
|
}
|
|
return (TRUE);
|
|
}
|
|
|
|
/* Register all the bits needed with the filtering engine */
|
|
void proto_register_aeron(void)
|
|
{
|
|
static hf_register_info hf[] =
|
|
{
|
|
{ &hf_aeron_channel_id,
|
|
{ "Channel ID", "aeron.channel_id", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_pad,
|
|
{ "Pad Frame", "aeron.pad", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_pad_frame_length,
|
|
{ "Frame Length", "aeron.pad.frame_length", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_pad_version,
|
|
{ "Version", "aeron.pad.version", FT_UINT8, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_pad_flags,
|
|
{ "Flags", "aeron.pad.flags", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_pad_type,
|
|
{ "Type", "aeron.pad.type", FT_UINT16, BASE_DEC_HEX, VALS(aeron_frame_type), 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_pad_term_offset,
|
|
{ "Term Offset", "aeron.pad.term_offset", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_pad_session_id,
|
|
{ "Session ID", "aeron.pad.session_id", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_pad_stream_id,
|
|
{ "Stream ID", "aeron.pad.stream_id", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_pad_term_id,
|
|
{ "Term ID", "aeron.pad.term_id", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_data,
|
|
{ "Data Frame", "aeron.data", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_data_frame_length,
|
|
{ "Frame Length", "aeron.data.frame_length", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_data_version,
|
|
{ "Version", "aeron.data.version", FT_UINT8, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_data_flags,
|
|
{ "Flags", "aeron.data.flags", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_data_flags_b,
|
|
{ "Begin Message", "aeron.data.flags.b", FT_BOOLEAN, 8, TFS(&tfs_set_notset), DATA_FLAGS_BEGIN, NULL, HFILL } },
|
|
{ &hf_aeron_data_flags_e,
|
|
{ "End Message", "aeron.data.flags.e", FT_BOOLEAN, 8, TFS(&tfs_set_notset), DATA_FLAGS_END, NULL, HFILL } },
|
|
{ &hf_aeron_data_type,
|
|
{ "Type", "aeron.data.type", FT_UINT16, BASE_DEC_HEX, VALS(aeron_frame_type), 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_data_term_offset,
|
|
{ "Term Offset", "aeron.data.term_offset", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_data_next_offset,
|
|
{ "Next Offset", "aeron.data.next_offset", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_data_next_offset_term,
|
|
{ "Next Offset Term", "aeron.data.next_offset_term", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_data_next_offset_first_frame,
|
|
{ "Next Offset First Frame", "aeron.data.next_offset_first_frame", FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_data_session_id,
|
|
{ "Session ID", "aeron.data.session_id", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_data_stream_id,
|
|
{ "Stream ID", "aeron.data.stream_id", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_data_term_id,
|
|
{ "Term ID", "aeron.data.term_id", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_data_reassembly,
|
|
{ "Reassembled Fragments", "aeron.data.reassembly", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_data_reassembly_fragment,
|
|
{ "Fragment", "aeron.data.reassembly.fragment", FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_nak,
|
|
{ "NAK Frame", "aeron.nak", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_nak_frame_length,
|
|
{ "Frame Length", "aeron.nak.frame_length", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_nak_version,
|
|
{ "Version", "aeron.nak.version", FT_UINT8, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_nak_flags,
|
|
{ "Flags", "aeron.nak.flags", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_nak_type,
|
|
{ "Type", "aeron.nak.type", FT_UINT16, BASE_DEC_HEX, VALS(aeron_frame_type), 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_nak_session_id,
|
|
{ "Session ID", "aeron.nak.session_id", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_nak_stream_id,
|
|
{ "Stream ID", "aeron.nak.stream_id", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_nak_term_id,
|
|
{ "Term ID", "aeron.nak.term_id", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_nak_term_offset,
|
|
{ "Term Offset", "aeron.nak.term_offset", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_nak_length,
|
|
{ "Length", "aeron.nak.length", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sm,
|
|
{ "Status Message", "aeron.sm", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sm_frame_length,
|
|
{ "Frame Length", "aeron.sm.frame_length", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sm_version,
|
|
{ "Version", "aeron.sm.version", FT_UINT8, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sm_flags,
|
|
{ "Flags", "aeron.sm.flags", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sm_flags_s,
|
|
{ "Setup", "aeron.sm.flags.s", FT_BOOLEAN, 8, TFS(&tfs_set_notset), STATUS_FLAGS_SETUP, NULL, HFILL } },
|
|
{ &hf_aeron_sm_type,
|
|
{ "Type", "aeron.sm.type", FT_UINT16, BASE_DEC_HEX, VALS(aeron_frame_type), 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sm_session_id,
|
|
{ "Session ID", "aeron.sm.session_id", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sm_stream_id,
|
|
{ "Stream ID", "aeron.sm.stream_id", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sm_consumption_term_id,
|
|
{ "Consumption Term ID", "aeron.sm.consumption_term_id", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sm_consumption_term_offset,
|
|
{ "Consumption Term Offset", "aeron.sm.consumption_term_offset", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sm_receiver_window,
|
|
{ "Receiver Window", "aeron.sm.receiver_window", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sm_feedback,
|
|
{ "Application-specific Feedback", "aeron.sm.feedback", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_err,
|
|
{ "Error Header", "aeron.err", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_err_frame_length,
|
|
{ "Frame Length", "aeron.err.frame_length", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_err_version,
|
|
{ "Version", "aeron.err.version", FT_UINT8, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_err_code,
|
|
{ "Error Code", "aeron.err.code", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_err_type,
|
|
{ "Type", "aeron.err.type", FT_UINT16, BASE_DEC_HEX, VALS(aeron_frame_type), 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_err_off_frame_length,
|
|
{ "Offending Frame Length", "aeron.err.off_frame_length", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_err_off_hdr,
|
|
{ "Offending Header", "aeron.err.off_hdr", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_err_string,
|
|
{ "Error String", "aeron.err.string", FT_STRINGZ, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_setup,
|
|
{ "Setup Frame", "aeron.setup", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_setup_frame_length,
|
|
{ "Frame Length", "aeron.setup.frame_length", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_setup_version,
|
|
{ "Version", "aeron.setup.version", FT_UINT8, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_setup_flags,
|
|
{ "Flags", "aeron.setup.flags", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_setup_type,
|
|
{ "Type", "aeron.setup.type", FT_UINT16, BASE_DEC_HEX, VALS(aeron_frame_type), 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_setup_term_offset,
|
|
{ "Term Offset", "aeron.setup.term_offset", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_setup_session_id,
|
|
{ "Session ID", "aeron.setup.session_id", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_setup_stream_id,
|
|
{ "Stream ID", "aeron.setup.stream_id", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_setup_initial_term_id,
|
|
{ "Initial Term ID", "aeron.setup.initial_term_id", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_setup_active_term_id,
|
|
{ "Active Term ID", "aeron.setup.active_term_id", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_setup_term_length,
|
|
{ "Term Length", "aeron.setup.term_length", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_setup_mtu,
|
|
{ "MTU", "aeron.setup.mtu", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sequence_analysis,
|
|
{ "Sequence Analysis", "aeron.sequence_analysis", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sequence_analysis_channel_prev_frame,
|
|
{ "Previous Channel Frame", "aeron.sequence_analysis.prev_channel_frame", FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sequence_analysis_channel_next_frame,
|
|
{ "Next Channel Frame", "aeron.sequence_analysis.next_channel_frame", FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sequence_analysis_stream_prev_frame,
|
|
{ "Previous Stream Frame", "aeron.sequence_analysis.prev_stream_frame", FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sequence_analysis_stream_next_frame,
|
|
{ "Next Stream Frame", "aeron.sequence_analysis.next_stream_frame", FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sequence_analysis_term_prev_frame,
|
|
{ "Previous Term Frame", "aeron.sequence_analysis.prev_term_frame", FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sequence_analysis_term_next_frame,
|
|
{ "Next Term Frame", "aeron.sequence_analysis.next_term_frame", FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sequence_analysis_term_offset,
|
|
{ "Offset also in", "aeron.sequence_analysis.term_offset", FT_NONE, BASE_NONE, NULL, 0x0, "Offset also appears in these frames", HFILL } },
|
|
{ &hf_aeron_sequence_analysis_term_offset_frame,
|
|
{ "Frame", "aeron.sequence_analysis.term_offset.frame", FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sequence_analysis_retransmission,
|
|
{ "Frame is a retransmission", "aeron.sequence_analysis.retransmission", FT_BOOLEAN, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sequence_analysis_retransmission_rx,
|
|
{ "List of NAK frames to which this retransmission applies", "aeron.sequence_analysis.retransmission.rx", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sequence_analysis_retransmission_rx_frame,
|
|
{ "Retransmission applies to frame", "aeron.sequence_analysis.retransmission.rx.frame", FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sequence_analysis_nak_unrecovered,
|
|
{ "Unrecovered Bytes", "aeron.sequence_analysis.nak_unrecovered", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sequence_analysis_nak_rx,
|
|
{ "List of RX Frames for this NAK", "aeron.sequence_analysis.nak_rx", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sequence_analysis_nak_rx_frame,
|
|
{ "RX Frame for this NAK", "aeron.sequence_analysis.nak_rx.frame", FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_sequence_analysis_keepalive,
|
|
{ "Frame is a keepalive", "aeron.sequence_analysis.keepalive", FT_BOOLEAN, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_stream_analysis,
|
|
{ "Stream Analysis", "aeron.stream_analysis", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_stream_analysis_high_term_id,
|
|
{ "Highest sent term ID", "aeron.stream_analysis.high_term_id", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_stream_analysis_high_term_offset,
|
|
{ "Highest sent term offset", "aeron.stream_analysis.high_term_offset", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_stream_analysis_completed_term_id,
|
|
{ "Completed term ID", "aeron.stream_analysis.completed_term_id", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_stream_analysis_completed_term_offset,
|
|
{ "Completed term offset", "aeron.stream_analysis.completed_term_offset", FT_UINT32, BASE_DEC_HEX, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_aeron_stream_analysis_outstanding_bytes,
|
|
{ "Outstanding bytes", "aeron.stream_analysis.outstanding_bytes", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }
|
|
};
|
|
static gint * ett[] =
|
|
{
|
|
&ett_aeron,
|
|
&ett_aeron_pad,
|
|
&ett_aeron_data,
|
|
&ett_aeron_data_flags,
|
|
&ett_aeron_data_reassembly,
|
|
&ett_aeron_nak,
|
|
&ett_aeron_sm,
|
|
&ett_aeron_sm_flags,
|
|
&ett_aeron_err,
|
|
&ett_aeron_setup,
|
|
&ett_aeron_ext,
|
|
&ett_aeron_sequence_analysis,
|
|
&ett_aeron_sequence_analysis_retransmission_rx,
|
|
&ett_aeron_sequence_analysis_nak_rx,
|
|
&ett_aeron_sequence_analysis_term_offset,
|
|
&ett_aeron_stream_analysis
|
|
};
|
|
static ei_register_info ei[] =
|
|
{
|
|
{ &ei_aeron_analysis_nak, { "aeron.analysis.nak", PI_SEQUENCE, PI_NOTE, "NAK", EXPFILL } },
|
|
{ &ei_aeron_analysis_window_full, { "aeron.analysis.window_full", PI_SEQUENCE, PI_NOTE, "Receiver window is full", EXPFILL } },
|
|
{ &ei_aeron_analysis_idle_rx, { "aeron.analysis.idle_rx", PI_SEQUENCE, PI_NOTE, "This frame contains an Idle RX", EXPFILL } },
|
|
{ &ei_aeron_analysis_pacing_rx, { "aeron.analysis.pacing_rx", PI_SEQUENCE, PI_NOTE, "This frame contains a Pacing RX", EXPFILL } },
|
|
{ &ei_aeron_analysis_ooo, { "aeron.analysis.ooo", PI_SEQUENCE, PI_NOTE, "This frame contains Out-of-order data", EXPFILL } },
|
|
{ &ei_aeron_analysis_ooo_gap, { "aeron.analysis.ooo_gap", PI_SEQUENCE, PI_NOTE, "This frame is an Out-of-order gap", EXPFILL } },
|
|
{ &ei_aeron_analysis_keepalive, { "aeron.analysis.keepalive", PI_SEQUENCE, PI_NOTE, "This frame contains a Keepalive", EXPFILL } },
|
|
{ &ei_aeron_analysis_window_resize, { "aeron.analysis.window_resize", PI_SEQUENCE, PI_NOTE, "Receiver window resized", EXPFILL } },
|
|
{ &ei_aeron_analysis_ooo_sm, { "aeron.analysis.ooo_sm", PI_SEQUENCE, PI_NOTE, "This frame contains an Out-of-order SM", EXPFILL } },
|
|
{ &ei_aeron_analysis_keepalive_sm, { "aeron.analysis.keepalive_sm", PI_SEQUENCE, PI_NOTE, "This frame contains a Keepalive SM", EXPFILL } },
|
|
{ &ei_aeron_analysis_rx, { "aeron.analysis.rx", PI_SEQUENCE, PI_NOTE, "This frame contains a (likely) retransmission", EXPFILL } },
|
|
{ &ei_aeron_analysis_term_id_change, { "aeron.analysis.term_id_change", PI_SEQUENCE, PI_CHAT, "This frame contains a new term ID", EXPFILL } },
|
|
{ &ei_aeron_analysis_invalid_pad_length, { "aeron.analysis.invalid_pad_length", PI_MALFORMED, PI_ERROR, "Invalid pad frame length", EXPFILL } },
|
|
{ &ei_aeron_analysis_invalid_data_length, { "aeron.analysis.invalid_data_length", PI_MALFORMED, PI_ERROR, "Invalid data frame length", EXPFILL } },
|
|
{ &ei_aeron_analysis_invalid_nak_length, { "aeron.analysis.invalid_nak_length", PI_MALFORMED, PI_ERROR, "Invalid NAK frame length", EXPFILL } },
|
|
{ &ei_aeron_analysis_invalid_sm_length, { "aeron.analysis.invalid_sm_length", PI_MALFORMED, PI_ERROR, "Invalid SM frame length", EXPFILL } },
|
|
{ &ei_aeron_analysis_invalid_err_length, { "aeron.analysis.invalid_err_length", PI_MALFORMED, PI_ERROR, "Invalid error frame length", EXPFILL } },
|
|
{ &ei_aeron_analysis_invalid_setup_length, { "aeron.analysis.invalid_setup_length", PI_MALFORMED, PI_ERROR, "Invalid setup frame length", EXPFILL } }
|
|
};
|
|
module_t * aeron_module;
|
|
expert_module_t * expert_aeron;
|
|
|
|
proto_aeron = proto_register_protocol("Aeron Protocol", "Aeron", "aeron");
|
|
|
|
proto_register_field_array(proto_aeron, hf, array_length(hf));
|
|
proto_register_subtree_array(ett, array_length(ett));
|
|
expert_aeron = expert_register_protocol(proto_aeron);
|
|
expert_register_field_array(expert_aeron, ei, array_length(ei));
|
|
aeron_module = prefs_register_protocol(proto_aeron, NULL);
|
|
aeron_heuristic_subdissector_list = register_heur_dissector_list("aeron_msg_payload", proto_aeron);
|
|
|
|
prefs_register_bool_preference(aeron_module,
|
|
"sequence_analysis",
|
|
"Analyze transport sequencing",
|
|
"Include next/previous frame for channel, stream, and term, and other transport sequence analysis.",
|
|
&aeron_sequence_analysis);
|
|
prefs_register_bool_preference(aeron_module,
|
|
"stream_analysis",
|
|
"Analyze stream sequencing",
|
|
"Include stream analysis, tracking publisher and subscriber positions. Requires \"Analyze transport sequencing\".",
|
|
&aeron_stream_analysis);
|
|
prefs_register_bool_preference(aeron_module,
|
|
"reassemble_fragments",
|
|
"Reassemble fragmented data",
|
|
"Reassemble fragmented data messages. Requires \"Analyze transport sequencing\" and \"Analyze stream sequencing\".",
|
|
&aeron_reassemble_fragments);
|
|
prefs_register_bool_preference(aeron_module,
|
|
"use_heuristic_subdissectors",
|
|
"Use heuristic sub-dissectors",
|
|
"Use a registered heuristic sub-dissector to decode the payload data. Requires \"Analyze transport sequencing\", \"Analyze stream sequencing\", and \"Reassemble fragmented data\".",
|
|
&aeron_use_heuristic_subdissectors);
|
|
register_init_routine(aeron_channel_id_init);
|
|
aeron_frame_info_tree = wmem_tree_new_autoreset(wmem_epan_scope(), wmem_file_scope());
|
|
}
|
|
|
|
/* The registration hand-off routine */
|
|
void proto_reg_handoff_aeron(void)
|
|
{
|
|
aeron_dissector_handle = create_dissector_handle(dissect_aeron, proto_aeron);
|
|
dissector_add_for_decode_as_with_preference("udp.port", aeron_dissector_handle);
|
|
heur_dissector_add("udp", test_aeron_packet, "Aeron over UDP", "aeron_udp", proto_aeron, HEURISTIC_DISABLE);
|
|
}
|
|
|
|
/*
|
|
* Editor modelines - http://www.wireshark.org/tools/modelines.html
|
|
*
|
|
* Local variables:
|
|
* c-basic-offset: 4
|
|
* tab-width: 8
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*
|
|
* vi: set shiftwidth=4 tabstop=8 expandtab:
|
|
* :indentSize=4:tabSize=8:noTabs=true:
|
|
*/
|