1 /* packet-epl-profile-parser.c
2 * Routines for reading in Ethernet POWERLINK XDD and CANopen EDS profiles
3 * (Ethernet POWERLINK XML Device Description (DS301) Draft Standard v1.2.0)
5 * Copyright (c) 2017: Karlsruhe Institute of Technology (KIT)
6 * Institute for Anthropomatics and Robotics (IAR)
7 * Intelligent Process Control and Robotics (IPR)
8 * http://rob.ipr.kit.edu/
10 * - Ahmad Fatoum <ahmad[AT]a3f.at>
12 * Wireshark - Network traffic analyzer
13 * By Gerald Combs <gerald@wireshark.org>
14 * Copyright 1998 Gerald Combs
16 * SPDX-License-Identifier: GPL-2.0-or-later
21 #include "packet-epl.h"
22 #include "ws_attributes.h"
24 #include <epan/ws_printf.h>
25 #include <epan/range.h>
30 #include <wsutil/strtoi.h>
31 #include <wsutil/str_util.h>
32 #include <wsutil/wslog.h>
33 #include <epan/wmem_scopes.h>
35 #if defined HAVE_LIBXML2
36 #include <libxml/xmlversion.h>
38 #if defined LIBXML_XPATH_ENABLED \
39 && defined LIBXML_SAX1_ENABLED \
40 && defined LIBXML_TREE_ENABLED
41 #include <libxml/tree.h>
42 #include <libxml/parser.h>
43 #include <libxml/xpath.h>
44 #include <libxml/xpathInternals.h>
48 typedef int xpath_handler(xmlNodeSetPtr
, void*);
49 static xpath_handler populate_object_list
, populate_datatype_list
, populate_profile_name
;
51 static struct xpath_namespace
{
52 const xmlChar
*prefix
, *href
;
54 { BAD_CAST
"x", BAD_CAST
"http://www.ethernet-powerlink.org" },
55 { BAD_CAST
"xsi", BAD_CAST
"http://www.w3.org/2001/XMLSchema-instance" },
61 xpath_handler
*handler
;
64 BAD_CAST
"//x:ISO15745Profile[x:ProfileHeader/x:ProfileIdentification='Powerlink_Communication_Profile']/x:ProfileHeader/x:ProfileName",
68 BAD_CAST
"//x:ProfileBody[@xsi:type='ProfileBody_CommunicationNetwork_Powerlink']/x:ApplicationLayers/x:DataTypeList/x:defType",
69 populate_datatype_list
72 BAD_CAST
"//x:ProfileBody[@xsi:type='ProfileBody_CommunicationNetwork_Powerlink']/x:ApplicationLayers/x:ObjectList/x:Object",
79 #endif /* LIBXML_XPATH_ENABLED && LIBXML_SAX1_ENABLED && LIBXML_TREE_ENABLED */
81 #endif /* HAVE_LIBXML2 */
85 const struct epl_datatype
*ptr
;
88 static struct typemap_entry
{
91 struct epl_datatype
*type
;
93 {0x0001, "Boolean", NULL
},
94 {0x0002, "Integer8", NULL
},
95 {0x0003, "Integer16", NULL
},
96 {0x0004, "Integer32", NULL
},
97 {0x0005, "Unsigned8", NULL
},
98 {0x0006, "Unsigned16", NULL
},
99 {0x0007, "Unsigned32", NULL
},
100 {0x0008, "Real32", NULL
},
101 {0x0009, "Visible_String", NULL
},
102 {0x0010, "Integer24", NULL
},
103 {0x0011, "Real64", NULL
},
104 {0x0012, "Integer40", NULL
},
105 {0x0013, "Integer48", NULL
},
106 {0x0014, "Integer56", NULL
},
107 {0x0015, "Integer64", NULL
},
108 {0x000A, "Octet_String", NULL
},
109 {0x000B, "Unicode_String", NULL
},
110 {0x000C, "Time_of_Day", NULL
},
111 {0x000D, "Time_Diff", NULL
},
112 {0x000F, "Domain", NULL
},
113 {0x0016, "Unsigned24", NULL
},
114 {0x0018, "Unsigned40", NULL
},
115 {0x0019, "Unsigned48", NULL
},
116 {0x001A, "Unsigned56", NULL
},
117 {0x001B, "Unsigned64", NULL
},
118 {0x0401, "MAC_ADDRESS", NULL
},
119 {0x0402, "IP_ADDRESS", NULL
},
120 {0x0403, "NETTIME", NULL
},
124 static wmem_map_t
*eds_typemap
;
126 struct epl_wmem_iarray
{
128 wmem_allocator_t
*scope
;
131 uint8_t is_sorted
:1;
134 static epl_wmem_iarray_t
*epl_wmem_iarray_new(wmem_allocator_t
*allocator
, const unsigned elem_size
, GEqualFunc cmp
) G_GNUC_MALLOC
;
135 static void epl_wmem_iarray_insert(epl_wmem_iarray_t
*iarr
, uint32_t where
, range_admin_t
*data
);
136 static void epl_wmem_iarray_sort_and_compact(epl_wmem_iarray_t
*iarr
);
139 epl_ishex(const char *num
)
141 if (g_str_has_prefix(num
, "0x"))
144 for (; g_ascii_isxdigit(*num
); num
++)
147 if (g_ascii_tolower(*num
) == 'h')
154 epl_g_key_file_get_uint16(GKeyFile
*gkf
, const char *group_name
, const char *key
, GError
**error
)
158 char *val
= g_key_file_get_string(gkf
, group_name
, key
, error
);
163 if (epl_ishex(val
)) /* We need to support XXh, but no octals (is that right?) */
164 ws_hexstrtou16(val
, &endptr
, &ret
);
166 ws_strtou16(val
, &endptr
, &ret
);
173 sort_subindices(void *key _U_
, void *value
, void *user_data _U_
)
175 epl_wmem_iarray_t
*subindices
= ((struct object
*)value
)->subindices
;
177 epl_wmem_iarray_sort_and_compact(subindices
);
183 struct typemap_entry
*entry
;
184 eds_typemap
= wmem_map_new(wmem_epan_scope(), g_direct_hash
, g_direct_equal
);
185 for (entry
= epl_datatypes
; entry
->name
; entry
++)
187 const struct epl_datatype
*type
= epl_type_to_hf(entry
->name
);
188 wmem_map_insert(eds_typemap
, GUINT_TO_POINTER(entry
->id
), (void*)type
);
193 epl_eds_load(struct profile
*profile
, const char *eds_file
)
197 char **group
, **groups
;
201 gkf
= g_key_file_new();
203 /* Load EDS document */
204 if (!g_key_file_load_from_file(gkf
, eds_file
, G_KEY_FILE_NONE
, &err
)){
205 ws_log(NULL
, LOG_LEVEL_WARNING
, "Error: unable to parse file \"%s\"\n", eds_file
);
210 profile
->path
= wmem_strdup(profile
->scope
, eds_file
);
212 val
= g_key_file_get_string(gkf
, "FileInfo", "Description", NULL
);
213 /* This leaves a trailing space, but that's ok */
214 profile
->name
= wmem_strndup(profile
->scope
, val
, strcspn(val
, "#"));
217 groups
= g_key_file_get_groups(gkf
, &groups_count
);
218 for (group
= groups
; *group
; group
++)
222 uint16_t idx
= 0, datatype
;
223 struct object
*obj
= NULL
;
224 struct od_entry tmpobj
= OD_ENTRY_INITIALIZER
;
225 bool is_object
= true;
227 if (!g_ascii_isxdigit(**group
))
230 ws_hexstrtou16(*group
, &endptr
, &idx
);
235 else if (g_str_has_prefix(endptr
, "sub"))
237 if (!ws_hexstrtou16(endptr
+ 3, &endptr
, &tmpobj
.idx
)
238 || tmpobj
.idx
> 0xFF)
245 tmpobj
.type_class
= epl_g_key_file_get_uint16(gkf
, *group
, "ObjectType", NULL
);
246 if (!tmpobj
.type_class
)
249 datatype
= epl_g_key_file_get_uint16(gkf
, *group
, "DataType", NULL
);
251 tmpobj
.type
= (const struct epl_datatype
*)wmem_map_lookup(eds_typemap
, GUINT_TO_POINTER(datatype
));
253 if ((name
= g_key_file_get_string(gkf
, *group
, "ParameterName", NULL
)))
255 size_t count
= strcspn(name
, "#") + 1;
259 count
> sizeof tmpobj
.name
? sizeof tmpobj
.name
: count
264 obj
= epl_profile_object_lookup_or_add(profile
, idx
);
267 { /* Let's add a new object! Exciting! */
271 { /* Object already there, let's add subindices */
272 struct subobject subobj
= SUBOBJECT_INITIALIZER
;
274 if (!obj
->subindices
)
276 obj
->subindices
= epl_wmem_iarray_new(
278 sizeof (struct subobject
),
283 subobj
.info
= tmpobj
;
284 epl_wmem_iarray_insert(obj
->subindices
, subobj
.info
.idx
, &subobj
.range
);
289 /* Unlike with XDDs, subindices might interleave with others, so let's sort them now */
290 wmem_map_foreach(profile
->objects
, sort_subindices
, NULL
);
292 /* We don't read object mappings from EDS files */
293 /* epl_profile_object_mappings_update(profile); */
296 g_key_file_free(gkf
);
303 epl_xdd_load(struct profile
*profile
, const char *xml_file
)
305 xmlXPathContextPtr xpathCtx
= NULL
;
307 struct xpath_namespace
*ns
= NULL
;
308 struct xpath
*xpath
= NULL
;
309 GHashTable
*typemap
= NULL
;
311 /* Load XML document */
312 doc
= xmlParseFile(xml_file
);
315 ws_log(NULL
, LOG_LEVEL_WARNING
, "Error: unable to parse file \"%s\"\n", xml_file
);
321 /* Create xpath evaluation context */
322 xpathCtx
= xmlXPathNewContext(doc
);
325 ws_log(NULL
, LOG_LEVEL_WARNING
, "Error: unable to create new XPath context\n");
330 /* Register namespaces from list */
331 for (ns
= namespaces
; ns
->href
; ns
++)
333 if(xmlXPathRegisterNs(xpathCtx
, ns
->prefix
, ns
->href
) != 0)
335 ws_log(NULL
, LOG_LEVEL_WARNING
, "Error: unable to register NS with prefix=\"%s\" and href=\"%s\"\n", ns
->prefix
, ns
->href
);
341 profile
->path
= wmem_strdup(profile
->scope
, xml_file
);
343 /* mapping type ids to &hf_s */
344 profile
->data
= typemap
= (GHashTable
*)g_hash_table_new_full(g_direct_hash
, g_direct_equal
, NULL
, g_free
);
346 /* Evaluate xpath expressions */
347 for (xpath
= xpaths
; xpath
->expr
; xpath
++)
349 xmlXPathObjectPtr xpathObj
= xmlXPathEvalExpression(xpath
->expr
, xpathCtx
);
350 if (!xpathObj
|| !xpathObj
->nodesetval
)
352 ws_log(NULL
, LOG_LEVEL_WARNING
, "Error: unable to evaluate xpath expression \"%s\"\n", xpath
->expr
);
353 xmlXPathFreeObject(xpathObj
);
359 if (xpath
->handler
&& xpathObj
->nodesetval
->nodeNr
)
360 xpath
->handler(xpathObj
->nodesetval
, profile
);
361 xmlXPathFreeObject(xpathObj
);
364 /* We create ObjectMappings while reading the XML, this is makes it likely,
365 * that we won't be able to reference a mapped object in the ObjectMapping
366 * as we didn't reach its XML tag yet. Therefore, after reading the XDD
367 * completely, we update mappings in the profile
369 epl_profile_object_mappings_update(profile
);
373 g_hash_table_destroy(typemap
);
376 xmlXPathFreeContext(xpathCtx
);
384 populate_profile_name(xmlNodeSetPtr nodes
, void *_profile
)
386 struct profile
*profile
= (struct profile
*)_profile
;
387 if (nodes
->nodeNr
== 1
388 && nodes
->nodeTab
[0]->type
== XML_ELEMENT_NODE
389 && nodes
->nodeTab
[0]->children
)
391 profile
->name
= wmem_strdup(profile
->scope
, (char*)nodes
->nodeTab
[0]->children
->content
);
399 populate_datatype_list(xmlNodeSetPtr nodes
, void *_profile
)
403 struct profile
*profile
= (struct profile
*)_profile
;
405 for(i
= 0; i
< nodes
->nodeNr
; ++i
)
409 if(!nodes
->nodeTab
[i
] || nodes
->nodeTab
[i
]->type
!= XML_ELEMENT_NODE
)
412 cur
= nodes
->nodeTab
[i
];
415 for(attr
= cur
->properties
; attr
; attr
= attr
->next
)
418 const char *key
= (const char*)attr
->name
;
419 const char *val
= (const char*)attr
->children
->content
;
421 if (g_str_equal("dataType", key
))
426 if (!ws_hexstrtou16(val
, &endptr
, &idx
))
429 for (subnode
= cur
->children
; subnode
; subnode
= subnode
->next
)
431 if (subnode
->type
== XML_ELEMENT_NODE
)
433 struct datatype
*type
;
434 const struct epl_datatype
*ptr
= epl_type_to_hf((const char*)subnode
->name
);
437 ws_log(NULL
, LOG_LEVEL_INFO
, "Skipping unknown type '%s'\n", subnode
->name
);
440 type
= g_new(struct datatype
, 1);
443 g_hash_table_insert((GHashTable
*)profile
->data
, GUINT_TO_POINTER(type
->id
), type
);
456 parse_obj_tag(xmlNode
*cur
, struct od_entry
*out
, struct profile
*profile
) {
458 const char *defaultValue
= NULL
, *actualValue
= NULL
;
461 for(attr
= cur
->properties
; attr
; attr
= attr
->next
)
463 const char *key
= (const char*)attr
->name
,
464 *val
= (const char*)attr
->children
->content
;
466 if (g_str_equal("index", key
))
468 if (!ws_hexstrtou16(val
, &endptr
, &out
->idx
))
471 } else if (g_str_equal("subIndex", key
)) {
472 if (!ws_hexstrtou16(val
, &endptr
, &out
->idx
))
475 } else if (g_str_equal("name", key
)) {
476 (void) g_strlcpy(out
->name
, val
, sizeof out
->name
);
478 } else if (g_str_equal("objectType", key
)) {
480 ws_hexstrtou16(val
, &endptr
, &out
->type_class
);
482 } else if (g_str_equal("dataType", key
)) {
484 if (ws_hexstrtou16(val
, &endptr
, &id
))
486 struct datatype
*type
= (struct datatype
*)g_hash_table_lookup((GHashTable
*)profile
->data
, GUINT_TO_POINTER(id
));
487 if (type
) out
->type
= type
->ptr
;
490 } else if (g_str_equal("defaultValue", key
)) {
493 } else if (g_str_equal("actualValue", key
)) {
497 else if (g_str_equal("PDOmapping", key
)) {
498 obj
.PDOmapping
= get_index(ObjectPDOmapping_tostr
, val
);
499 assert(obj
.PDOmapping
>= 0);
505 out
->value
= g_ascii_strtoull(actualValue
, NULL
, 0);
506 else if (defaultValue
)
507 out
->value
= g_ascii_strtoull(defaultValue
, NULL
, 0);
516 populate_object_list(xmlNodeSetPtr nodes
, void *_profile
)
519 struct profile
*profile
= (struct profile
*)_profile
;
521 for(i
= 0; i
< nodes
->nodeNr
; ++i
)
523 xmlNodePtr cur
= nodes
->nodeTab
[i
];
524 struct od_entry tmpobj
= OD_ENTRY_INITIALIZER
;
526 if (!nodes
->nodeTab
[i
] || nodes
->nodeTab
[i
]->type
!= XML_ELEMENT_NODE
)
529 parse_obj_tag(cur
, &tmpobj
, profile
);
533 struct object
*obj
= epl_profile_object_add(profile
, tmpobj
.idx
);
536 if (tmpobj
.type_class
== OD_ENTRY_ARRAY
|| tmpobj
.type_class
== OD_ENTRY_RECORD
)
539 struct subobject subobj
= SUBOBJECT_INITIALIZER
;
541 obj
->subindices
= epl_wmem_iarray_new(profile
->scope
, sizeof (struct subobject
), subobject_equal
);
543 for (subcur
= cur
->children
; subcur
; subcur
= subcur
->next
)
545 if (subcur
->type
!= XML_ELEMENT_NODE
)
548 if (parse_obj_tag(subcur
, &subobj
.info
, profile
))
550 epl_wmem_iarray_insert(obj
->subindices
,
551 subobj
.info
.idx
, &subobj
.range
);
553 if (subobj
.info
.value
&& epl_profile_object_mapping_add(
554 profile
, obj
->info
.idx
, (uint8_t)subobj
.info
.idx
, subobj
.info
.value
))
556 ws_log(NULL
, LOG_LEVEL_INFO
,
557 "Loaded mapping from XDC %s:%s", obj
->info
.name
, subobj
.info
.name
);
560 epl_wmem_iarray_sort_and_compact(obj
->subindices
);
569 #else /* ! PARSE_XDD */
573 epl_xdd_load(struct profile
*profile _U_
, const char *xml_file _U_
)
577 #endif /* HAVE_LIBXML2 */
579 #endif /* ! PARSE_XDD */
582 * A sorted array keyed by intervals
583 * You keep inserting items, then sort the array.
584 * sorting also combines items that compare equal into one and adjusts
585 * the interval accordingly. find uses binary search to find the item
587 * This is particularly useful, if many similar items exist adjacent to each other
588 * e.g. ObjectMapping subindices in EPL XDD (packet-epl-profile-parser.c)
590 * Interval Trees wouldn't work for this scenario, because they don't allow
591 * expansion of existing intervals. Using an array instead of a tree,
592 * may additionally offer a possible performance advantage
594 * Much room for optimization in the creation process of the array,
595 * but we assume this to be an infrequent operation, with space utilization and
596 * finding speed being more important.
601 free_garray(wmem_allocator_t
*scope _U_
, wmem_cb_event_t event _U_
, void *data
)
603 GArray
*arr
= (GArray
*)data
;
604 g_array_free(arr
, true);
609 * \param scope wmem pool to use
610 * \param elem_size size of elements to add into the iarray
611 * \param equal establishes whether two adjacent elements are equal and thus
612 * shall be combined at sort-time
614 * \returns a new interval array or NULL on failure
616 * Creates a new interval array.
617 * Elements must have a range_admin_t as their first element,
618 * which will be managed by the implementation.
619 * \NOTE The cmp parameter can be used to free resources. When combining,
620 * it's always the second argument that's getting removed.
623 static epl_wmem_iarray_t
*
624 epl_wmem_iarray_new(wmem_allocator_t
*scope
, const unsigned elem_size
, GEqualFunc equal
)
626 epl_wmem_iarray_t
*iarr
;
628 if (elem_size
< sizeof(range_t
)) return NULL
;
630 iarr
= wmem_new(scope
, epl_wmem_iarray_t
);
631 if (!iarr
) return NULL
;
635 iarr
->arr
= g_array_new(false, false, elem_size
);
636 iarr
->is_sorted
= true;
638 wmem_register_callback(scope
, free_garray
, iarr
->arr
);
644 /** Returns true if the iarr is empty. */
646 epl_wmem_iarray_is_empty(epl_wmem_iarray_t
*iarr
)
648 return iarr
->arr
->len
== 0;
651 /** Returns true if the iarr is sorted. */
653 epl_wmem_iarray_is_sorted(epl_wmem_iarray_t
*iarr
)
655 return iarr
->is_sorted
;
658 /** Inserts an element */
660 epl_wmem_iarray_insert(epl_wmem_iarray_t
*iarr
, uint32_t where
, range_admin_t
*data
)
663 iarr
->is_sorted
= false;
665 data
->high
= data
->low
= where
;
666 g_array_append_vals(iarr
->arr
, data
, 1);
669 static int u32cmp(uint32_t a
, uint32_t b
)
671 if (a
< b
) return -1;
672 if (a
> b
) return +1;
678 epl_wmem_iarray_cmp(const void *a
, const void *b
)
680 return u32cmp(*(const uint32_t*)a
, *(const uint32_t*)b
);
683 /** Makes array suitable for searching */
685 epl_wmem_iarray_sort_and_compact(epl_wmem_iarray_t
*iarr
)
687 range_admin_t
*elem
, *prev
= NULL
;
689 len
= iarr
->arr
->len
;
693 g_array_sort(iarr
->arr
, epl_wmem_iarray_cmp
);
694 prev
= elem
= (range_admin_t
*)iarr
->arr
->data
;
696 for (i
= 1; i
< len
; i
++) {
697 elem
= (range_admin_t
*)((char*)elem
+ g_array_get_element_size(iarr
->arr
));
699 /* neighbours' range must be within one of each other and their content equal */
700 while (i
< len
&& elem
->low
- prev
->high
<= 1 && iarr
->equal(elem
, prev
)) {
701 prev
->high
= elem
->high
;
703 g_array_remove_index(iarr
->arr
, i
);
713 find_in_range(const void *_a
, const void *_b
)
715 const range_admin_t
*a
= (const range_admin_t
*)_a
,
716 *b
= (const range_admin_t
*)_b
;
718 if (a
->low
<= b
->high
&& b
->low
<= a
->high
) /* overlap */
721 return u32cmp(a
->low
, b
->low
);
725 bsearch_garray(const void *key
, GArray
*arr
, int (*cmp
)(const void*, const void*))
727 return bsearch(key
, arr
->data
, arr
->len
, g_array_get_element_size(arr
), cmp
);
731 * Finds an element in the interval array. Returns NULL if it doesn't exist
732 * Calling this is unspecified if the array wasn't sorted before
735 epl_wmem_iarray_find(epl_wmem_iarray_t
*iarr
, uint32_t value
) {
736 epl_wmem_iarray_sort_and_compact(iarr
);
738 range_admin_t needle
;
741 return (range_admin_t
*)bsearch_garray(&needle
, iarr
->arr
, find_in_range
);
746 epl_wmem_print_iarr(epl_wmem_iarray_t
*iarr
)
750 elem
= (range_admin_t
*)iarr
->arr
->data
;
751 len
= iarr
->arr
->len
;
752 for (i
= 0; i
< len
; i
++)
755 ws_debug_printf("Range: low=%" PRIu32
" high=%" PRIu32
"\n", elem
->low
, elem
->high
);
757 elem
= (range_admin_t
*)((char*)elem
+ g_array_get_element_size(iarr
->arr
));
763 * Editor modelines - https://www.wireshark.org/tools/modelines.html
768 * indent-tabs-mode: t
771 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
772 * :indentSize=8:tabSize=8:noTabs=false: