packet-epl.c: Enhance dissection by ObjectMappings and device profiles

Cyclic PDOs are setup either by ObjectMappings in the asynchronous SDOs,
or by serialized ObjectMappings in device profile files.

We now keep track of ObjectMappings transmitted via SDOs or read from
XDC files and use those to correctly partition the PDO's payloads.

Additionally types and descriptions for Object Directory entries extracted
from the EDS and XDD profiles are used to select the correct Wireshark type
and a string representation for those partitoned PDOs. Other places where
indices and subindices are also enriched by this information.

EDS support leverages GKeyFile and is available unconditionally, XDD/XDC
parsing support depends on the availabilty of libxml2. A patch for
inclusion of the latter as optional dependency was submitted
as Change-Id: I13c0a2f408fb5c21bad7ab3d7971e0fa8ed7d783

Electronic Data Sheet (EDS) is the CANopen standard for device profiles,
POWERLINK being based on CANopen, is occasionly used with EDS profiles.

XML Device Description (XDD) is the Ethernet POWERLINK standard for
device profiles. XDC have the same structure but contain actualValues
fields which can contain default ObjectMappings.

XML Device Descriptions can be 25k+ lines with much duplication,
so wmem_iarray_t is leveraged for saving space as well as faster lookups.

A side-effect of now organizing the capture in conversations is that
POWERLINK over UDP packets are now assigned proper destination and source
node IDs, which are displayed in the column view. The Referenced bug where
packets where erronously flagged as duplicates because the address wasn't
considered is also fixed as a result.

Bug: 13604
Bug: 13749
Change-Id: Ic33ff0be8f2eae7c24fe5877ad9258d1e550c227
Reviewed-on: https://code.wireshark.org/review/21112
Petri-Dish: Peter Wu <peter@lekensteyn.nl>
Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org>
Reviewed-by: Peter Wu <peter@lekensteyn.nl>
This commit is contained in:
Ahmad Fatoum 2017-06-01 11:11:18 +02:00 committed by Peter Wu
parent b2302d7a35
commit 9a85e30668
3 changed files with 2509 additions and 301 deletions

View File

@ -39,12 +39,100 @@
#include <string.h>
#include <stdlib.h>
#include <wsutil/strtoi.h>
#include <wsutil/str_util.h>
#include <epan/wmem/wmem.h>
/* XXX: Temporary. successive related change makes use of the functions here */
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wunused-function"
#endif
#if defined HAVE_LIBXML2
#include <libxml/xmlversion.h>
#if defined LIBXML_XPATH_ENABLED \
&& defined LIBXML_SAX1_ENABLED \
&& defined LIBXML_TREE_ENABLED
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#define PARSE_XDD 1
typedef int xpath_handler(xmlNodeSetPtr, void*);
static xpath_handler populate_object_list, populate_datatype_list, populate_profile_name;
static struct xpath_namespace {
const xmlChar *prefix, *href;
} namespaces[] = {
{ BAD_CAST "x", BAD_CAST "http://www.ethernet-powerlink.org" },
{ BAD_CAST "xsi", BAD_CAST "http://www.w3.org/2001/XMLSchema-instance" },
{ NULL, NULL }
};
static struct xpath {
const xmlChar *expr;
xpath_handler *handler;
} xpaths[] = {
{
BAD_CAST "//x:ISO15745Profile[x:ProfileHeader/x:ProfileIdentification='Powerlink_Communication_Profile']/x:ProfileHeader/x:ProfileName",
populate_profile_name
},
{
BAD_CAST "//x:ProfileBody[@xsi:type='ProfileBody_CommunicationNetwork_Powerlink']/x:ApplicationLayers/x:DataTypeList/x:defType",
populate_datatype_list
},
{
BAD_CAST "//x:ProfileBody[@xsi:type='ProfileBody_CommunicationNetwork_Powerlink']/x:ApplicationLayers/x:ObjectList/x:Object",
populate_object_list
},
{ NULL, NULL }
};
#endif /* LIBXML_XPATH_ENABLED && LIBXML_SAX1_ENABLED && LIBXML_TREE_ENABLED */
#endif /* HAVE_LIBXML2 */
struct datatype {
guint16 id;
const struct epl_datatype *ptr;
};
static struct typemap_entry {
guint16 id;
const char *name;
struct epl_datatype *type;
} epl_datatypes[] = {
{0x0001, "Boolean", NULL},
{0x0002, "Integer8", NULL},
{0x0003, "Integer16", NULL},
{0x0004, "Integer32", NULL},
{0x0005, "Unsigned8", NULL},
{0x0006, "Unsigned16", NULL},
{0x0007, "Unsigned32", NULL},
{0x0008, "Real32", NULL},
{0x0009, "Visible_String", NULL},
{0x0010, "Integer24", NULL},
{0x0011, "Real64", NULL},
{0x0012, "Integer40", NULL},
{0x0013, "Integer48", NULL},
{0x0014, "Integer56", NULL},
{0x0015, "Integer64", NULL},
{0x000A, "Octet_String", NULL},
{0x000B, "Unicode_String", NULL},
{0x000C, "Time_of_Day", NULL},
{0x000D, "Time_Diff", NULL},
{0x000F, "Domain", NULL},
{0x0016, "Unsigned24", NULL},
{0x0018, "Unsigned40", NULL},
{0x0019, "Unsigned48", NULL},
{0x001A, "Unsigned56", NULL},
{0x001B, "Unsigned64", NULL},
{0x0401, "MAC_ADDRESS", NULL},
{0x0402, "IP_ADDRESS", NULL},
{0x0403, "NETTIME", NULL},
{0x0000, NULL, NULL}
};
static wmem_map_t *eds_typemap;
struct epl_wmem_iarray {
GEqualFunc equal;
@ -55,12 +143,446 @@ struct epl_wmem_iarray {
};
static epl_wmem_iarray_t *epl_wmem_iarray_new(wmem_allocator_t *allocator, const guint elem_size, GEqualFunc cmp) G_GNUC_MALLOC;
static gboolean epl_wmem_iarray_is_empty(epl_wmem_iarray_t *iarr);
static gboolean epl_wmem_iarray_is_sorted(epl_wmem_iarray_t *iarr);
gboolean epl_wmem_iarray_is_empty(epl_wmem_iarray_t *iarr);
gboolean epl_wmem_iarray_is_sorted(epl_wmem_iarray_t *iarr);
static void epl_wmem_iarray_insert(epl_wmem_iarray_t *iarr, guint32 where, range_admin_t *data);
static void epl_wmem_iarray_sort_and_compact(epl_wmem_iarray_t *iarr);
static range_admin_t * epl_wmem_iarray_find(epl_wmem_iarray_t *arr, guint32 value);
static gboolean
epl_ishex(const char *num)
{
if (g_str_has_prefix(num, "0x"))
return TRUE;
for (; g_ascii_isxdigit(*num); num++)
;
if (g_ascii_tolower(*num) == 'h')
return TRUE;
return FALSE;
}
static guint16
epl_g_key_file_get_uint16(GKeyFile *gkf, const gchar *group_name, const gchar *key, GError **error)
{
guint16 ret = 0;
const char *endptr;
char *val = g_key_file_get_string(gkf, group_name, key, error);
if (!val)
return 0;
if (epl_ishex(val)) /* We need to support XXh, but no octals (is that right?) */
ws_hexstrtou16(val, &endptr, &ret);
else
ws_strtou16(val, &endptr, &ret);
g_free(val);
return ret;
}
static void
sort_subindices(void *key _U_, void *value, void *user_data _U_)
{
epl_wmem_iarray_t *subindices = ((struct object*)value)->subindices;
if (subindices)
epl_wmem_iarray_sort_and_compact(subindices);
}
void
epl_eds_init(void)
{
struct typemap_entry *entry;
eds_typemap = wmem_map_new(wmem_epan_scope(), g_direct_hash, g_direct_equal);
for (entry = epl_datatypes; entry->name; entry++)
{
const struct epl_datatype *type = epl_type_to_hf(entry->name);
wmem_map_insert(eds_typemap, GUINT_TO_POINTER(entry->id), (void*)type);
}
}
struct profile *
epl_eds_load(struct profile *profile, const char *eds_file)
{
GKeyFile* gkf;
GError *err;
char **group, **groups;
char *val;
gsize groups_count;
gkf = g_key_file_new();
/* Load EDS document */
if (!g_key_file_load_from_file(gkf, eds_file, G_KEY_FILE_NONE, &err)){
g_log(NULL, G_LOG_LEVEL_WARNING, "Error: unable to parse file \"%s\"\n", eds_file);
profile = NULL;
goto cleanup;
}
profile->path = wmem_strdup(profile->scope, eds_file);
val = g_key_file_get_string(gkf, "FileInfo", "Description", NULL);
/* This leaves a trailing space, but that's ok */
profile->name = wmem_strndup(profile->scope, val, strcspn(val, "#"));
g_free(val);
groups = g_key_file_get_groups(gkf, &groups_count);
for (group = groups; *group; group++)
{
char *name;
const char *endptr;
guint16 idx = 0, datatype;
struct object *obj = NULL;
struct od_entry tmpobj = {0};
gboolean is_object = TRUE;
if (!g_ascii_isxdigit(**group))
continue;
ws_hexstrtou16(*group, &endptr, &idx);
if (*endptr == '\0')
{ /* index */
tmpobj.idx = idx;
}
else if (g_str_has_prefix(endptr, "sub"))
{ /* subindex */
if (!ws_hexstrtou16(endptr + 3, &endptr, &tmpobj.idx)
|| tmpobj.idx > 0xFF)
continue;
is_object = FALSE;
}
else continue;
tmpobj.type_class = epl_g_key_file_get_uint16(gkf, *group, "ObjectType", NULL);
if (!tmpobj.type_class)
continue;
datatype = epl_g_key_file_get_uint16(gkf, *group, "DataType", NULL);
if (datatype)
tmpobj.type = (const struct epl_datatype*)wmem_map_lookup(eds_typemap, GUINT_TO_POINTER(datatype));
if ((name = g_key_file_get_string(gkf, *group, "ParameterName", NULL)))
{
gsize count = strcspn(name, "#") + 1;
g_strlcpy(
tmpobj.name,
name,
count > sizeof tmpobj.name ? sizeof tmpobj.name : count
);
g_free(name);
}
obj = epl_profile_object_lookup_or_add(profile, idx);
if (is_object)
{ /* Let's add a new object! Exciting! */
obj->info = tmpobj;
}
else
{ /* Object already there, let's add subindices */
struct subobject subobj = {0};
if (!obj->subindices)
{
obj->subindices = epl_wmem_iarray_new(
profile->scope,
sizeof (struct subobject),
subobject_equal
);
}
subobj.info = tmpobj;
epl_wmem_iarray_insert(obj->subindices, subobj.info.idx, &subobj.range);
}
}
/* Unlike with XDDs, subindices might interleave with others, so let's sort them now */
wmem_map_foreach(profile->objects, sort_subindices, NULL);
/* We don't read object mappings from EDS files */
/* epl_profile_object_mappings_update(profile); */
cleanup:
g_key_file_free(gkf);
return profile;
}
#ifdef PARSE_XDD
void
epl_xdd_init(void)
{
}
struct profile *
epl_xdd_load(struct profile *profile, const char *xml_file)
{
xmlXPathContextPtr xpathCtx = NULL;
xmlDoc *doc = NULL;
struct xpath_namespace *ns = NULL;
struct xpath *xpath = NULL;
GHashTable *typemap = NULL;
/* Load XML document */
doc = xmlParseFile(xml_file);
if (!doc)
{
g_log(NULL, G_LOG_LEVEL_WARNING, "Error: unable to parse file \"%s\"\n", xml_file);
profile = NULL;
goto cleanup;
}
/* Create xpath evaluation context */
xpathCtx = xmlXPathNewContext(doc);
if(!xpathCtx)
{
g_log(NULL, G_LOG_LEVEL_WARNING, "Error: unable to create new XPath context\n");
profile = NULL;
goto cleanup;
}
/* Register namespaces from list */
for (ns = namespaces; ns->href; ns++)
{
if(xmlXPathRegisterNs(xpathCtx, ns->prefix, ns->href) != 0)
{
g_log(NULL, G_LOG_LEVEL_WARNING, "Error: unable to register NS with prefix=\"%s\" and href=\"%s\"\n", ns->prefix, ns->href);
profile = NULL;
goto cleanup;
}
}
profile->path = wmem_strdup(profile->scope, xml_file);
/* mapping type ids to &hf_s */
profile->data = typemap = (GHashTable*)g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
/* Evaluate xpath expressions */
for (xpath = xpaths; xpath->expr; xpath++)
{
xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression(xpath->expr, xpathCtx);
if (!xpathObj || !xpathObj->nodesetval)
{
g_log(NULL, G_LOG_LEVEL_WARNING, "Error: unable to evaluate xpath expression \"%s\"\n", xpath->expr);
xmlXPathFreeObject(xpathObj);
profile = NULL;
goto cleanup;
}
/* run handler */
if (xpath->handler && xpathObj->nodesetval->nodeNr)
xpath->handler(xpathObj->nodesetval, profile);
xmlXPathFreeObject(xpathObj);
}
/* We create ObjectMappings while reading the XML, this is makes it likely,
* that we won't be able to reference a mapped object in the ObjectMapping
* as we didn't reach its XML tag yet. Therefore, after reading the XDD
* completely, we update mappings in the profile
*/
epl_profile_object_mappings_update(profile);
cleanup:
if (typemap)
g_hash_table_destroy(typemap);
if (xpathCtx)
xmlXPathFreeContext(xpathCtx);
if (doc)
xmlFreeDoc(doc);
return profile;
}
static int
populate_profile_name(xmlNodeSetPtr nodes, void *_profile)
{
struct profile *profile = (struct profile*)_profile;
if (nodes->nodeNr == 1
&& nodes->nodeTab[0]->type == XML_ELEMENT_NODE
&& nodes->nodeTab[0]->children)
{
profile->name = wmem_strdup(profile->scope, (char*)nodes->nodeTab[0]->children->content);
return 0;
}
return -1;
}
static int
populate_datatype_list(xmlNodeSetPtr nodes, void *_profile)
{
xmlNodePtr cur;
int i;
struct profile *profile = (struct profile*)_profile;
for(i = 0; i < nodes->nodeNr; ++i)
{
xmlAttrPtr attr;
if(!nodes->nodeTab[i] || nodes->nodeTab[i]->type != XML_ELEMENT_NODE)
return -1;
cur = nodes->nodeTab[i];
for(attr = cur->properties; attr; attr = attr->next)
{
const char *endptr;
const char *key = (const char*)attr->name;
const char *val = (const char*)attr->children->content;
if (g_str_equal("dataType", key))
{
xmlNode *subnode;
guint16 idx = 0;
if (!ws_hexstrtou16(val, &endptr, &idx))
continue;
for (subnode = cur->children; subnode; subnode = subnode->next)
{
if (subnode->type == XML_ELEMENT_NODE)
{
struct datatype *type;
const struct epl_datatype *ptr = epl_type_to_hf((char*)subnode->name);
if (!ptr)
{
g_log(NULL, G_LOG_LEVEL_INFO, "Skipping unknown type '%s'\n", subnode->name);
continue;
}
type = g_new(struct datatype, 1);
type->id = idx;
type->ptr = ptr;
g_hash_table_insert((GHashTable*)profile->data, GUINT_TO_POINTER(type->id), type);
continue;
}
}
}
}
}
return 0;
}
static gboolean
parse_obj_tag(xmlNode *cur, struct od_entry *out, struct profile *profile) {
xmlAttrPtr attr;
const char *defaultValue = NULL, *actualValue = NULL;
const char *endptr;
for(attr = cur->properties; attr; attr = attr->next)
{
const char *key = (char*)attr->name,
*val = (char*)attr->children->content;
if (g_str_equal("index", key))
{
if (!ws_hexstrtou16(val, &endptr, &out->idx))
return FALSE;
} else if (g_str_equal("subIndex", key)) {
if (!ws_hexstrtou16(val, &endptr, &out->idx))
return FALSE;
} else if (g_str_equal("name", key)) {
g_strlcpy(out->name, val, sizeof out->name);
} else if (g_str_equal("objectType", key)) {
out->type_class = 0;
ws_hexstrtou16(val, &endptr, &out->type_class);
} else if (g_str_equal("dataType", key)) {
guint16 id;
if (ws_hexstrtou16(val, &endptr, &id))
{
struct datatype *type = (struct datatype*)g_hash_table_lookup((GHashTable*)profile->data, GUINT_TO_POINTER(id));
if (type) out->type = type->ptr;
}
} else if (g_str_equal("defaultValue", key)) {
defaultValue = val;
} else if (g_str_equal("actualValue", key)) {
actualValue = val;
}
#if 0
else if (g_str_equal("PDOmapping", key)) {
obj.PDOmapping = get_index(ObjectPDOmapping_tostr, val);
assert(obj.PDOmapping >= 0);
}
#endif
}
if (actualValue)
out->value = g_ascii_strtoull(actualValue, NULL, 0);
else if (defaultValue)
out->value = g_ascii_strtoull(defaultValue, NULL, 0);
else
out->value = 0;
return TRUE;
}
static int
populate_object_list(xmlNodeSetPtr nodes, void *_profile)
{
int i;
struct profile *profile = (struct profile*)_profile;
for(i = 0; i < nodes->nodeNr; ++i)
{
xmlNodePtr cur = nodes->nodeTab[i];
struct od_entry tmpobj = {0};
if (!nodes->nodeTab[i] || nodes->nodeTab[i]->type != XML_ELEMENT_NODE)
continue;
parse_obj_tag(cur, &tmpobj, profile);
if (tmpobj.idx)
{
struct object *obj = epl_profile_object_add(profile, tmpobj.idx);
obj->info = tmpobj;
if (tmpobj.type_class == OD_ENTRY_ARRAY || tmpobj.type_class == OD_ENTRY_RECORD)
{
xmlNode *subcur;
struct subobject subobj = {0};
obj->subindices = epl_wmem_iarray_new(profile->scope, sizeof (struct subobject), subobject_equal);
for (subcur = cur->children; subcur; subcur = subcur->next)
{
if (subcur->type != XML_ELEMENT_NODE)
continue;
if (parse_obj_tag(subcur, &subobj.info, profile))
{
epl_wmem_iarray_insert(obj->subindices,
subobj.info.idx, &subobj.range);
}
if (subobj.info.value && epl_profile_object_mapping_add(
profile, obj->info.idx, (guint8)subobj.info.idx, subobj.info.value))
{
g_log(NULL, G_LOG_LEVEL_INFO,
"Loaded mapping from XDC %s:%s", obj->info.name, subobj.info.name);
}
}
epl_wmem_iarray_sort_and_compact(obj->subindices);
}
}
}
return 0;
}
#endif /* PARSE_XDD */
/**
* A sorted array keyed by intervals
@ -104,7 +626,7 @@ free_garray(wmem_allocator_t *scope _U_, wmem_cb_event_t event _U_, void *data)
* it's always the second argument that's getting removed.
*/
epl_wmem_iarray_t *
static epl_wmem_iarray_t *
epl_wmem_iarray_new(wmem_allocator_t *scope, const guint elem_size, GEqualFunc equal)
{
epl_wmem_iarray_t *iarr;
@ -126,21 +648,21 @@ epl_wmem_iarray_new(wmem_allocator_t *scope, const guint elem_size, GEqualFunc e
/** Returns true if the iarr is empty. */
static gboolean
gboolean
epl_wmem_iarray_is_empty(epl_wmem_iarray_t *iarr)
{
return iarr->arr->len == 0;
}
/** Returns true if the iarr is sorted. */
static gboolean
gboolean
epl_wmem_iarray_is_sorted(epl_wmem_iarray_t *iarr)
{
return iarr->is_sorted;
}
/** Inserts an element */
void
static void
epl_wmem_iarray_insert(epl_wmem_iarray_t *iarr, guint32 where, range_admin_t *data)
{
if (iarr->arr->len)
@ -157,7 +679,7 @@ epl_wmem_iarray_cmp(const void *a, const void *b)
}
/** Makes array suitable for searching */
void
static void
epl_wmem_iarray_sort_and_compact(epl_wmem_iarray_t *iarr)
{
range_admin_t *elem, *prev = NULL;
@ -207,7 +729,7 @@ bsearch_garray(const void *key, GArray *arr, int (*cmp)(const void*, const void*
* Finds an element in the interval array. Returns NULL if it doesn't exist
* Calling this is unspecified if the array wasn't sorted before
*/
static range_admin_t *
range_admin_t *
epl_wmem_iarray_find(epl_wmem_iarray_t *iarr, guint32 value) {
epl_wmem_iarray_sort_and_compact(iarr);

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,102 @@
#include <epan/wmem/wmem.h>
#include <epan/range.h>
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#pragma GCC diagnostic ignored "-Wmissing-braces"
#endif
struct epl_datatype;
struct profile {
guint16 id;
guint8 nodeid;
address node_addr;
guint32 vendor_id;
guint32 product_code;
wmem_map_t *objects;
wmem_allocator_t *scope, *parent_scope;
wmem_map_t *parent_map;
char *name;
char *path;
void *data;
guint cb_id;
wmem_array_t *TPDO; /* CN->MN */
wmem_array_t *RPDO; /* MN->CN */
struct profile *next;
};
enum { OD_ENTRY_SCALAR = 7, OD_ENTRY_ARRAY = 8, OD_ENTRY_RECORD = 9 };
struct od_entry {
guint16 idx;
/* This is called the ObjectType in the standard,
* but this is too easy to be mistaken with the
* DataType.
* ObjectType specifies whether it's a scalar or
* an aggregate
*/
guint16 type_class;
char name[64];
/* Called DataType by the standard,
* Can be e.g. Unsigned32
*/
const struct epl_datatype *type;
guint64 value;
};
struct subobject {
range_admin_t range;
struct od_entry info;
};
typedef struct epl_wmem_iarray epl_wmem_iarray_t;
struct object {
struct od_entry info;
epl_wmem_iarray_t *subindices;
};
struct profile;
const struct epl_datatype *epl_type_to_hf(const char *name);
static inline gboolean
subobject_equal(gconstpointer _a, gconstpointer _b)
{
const struct od_entry *a = &((const struct subobject*)_a)->info;
const struct od_entry *b = &((const struct subobject*)_b)->info;
return a->type_class == b->type_class
&& a->type == b->type
&& g_str_equal(a->name, b->name);
}
void epl_xdd_init(void);
struct profile *epl_xdd_load(struct profile *profile, const char *xml_file);
void epl_eds_init(void);
struct profile *epl_eds_load(struct profile *profile, const char *eds_file);
struct object *epl_profile_object_add(struct profile *profile, guint16 idx);
struct object *epl_profile_object_lookup_or_add(struct profile *profile, guint16 idx);
gboolean epl_profile_object_mapping_add(struct profile *profile, guint16 idx, guint8 subindex, guint64 mapping);
gboolean epl_profile_object_mappings_update(struct profile *profile);
range_admin_t * epl_wmem_iarray_find(epl_wmem_iarray_t *arr, guint32 value);
gboolean epl_wmem_iarray_is_empty(epl_wmem_iarray_t *iarr);
gboolean epl_wmem_iarray_is_sorted(epl_wmem_iarray_t *iarr);
#define EPL_OBJECT_MAPPING_SIZE sizeof (guint64)
#define CHECK_OVERLAP_ENDS(x1, x2, y1, y2) ((x1) < (y2) && (y1) < (x2))
#define CHECK_OVERLAP_LENGTH(x, x_len, y, y_len) \
CHECK_OVERLAP_ENDS((x), (x) + (x_len), (y), (y) + (y_len))
#endif