Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/cbexigen/FileGenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ def __generate_static_h(self, parameters):
generator = tools_generator.get_generator()
temp = generator.get_template(config['template'])
code = temp.render(filename=config['filename'], filekey=config['identifier'],
add_debug_code=self.__analyzer_data.add_debug_code_enabled)
add_debug_code=self.__analyzer_data.add_debug_code_enabled,
canonical_exi_enabled=tools_conf.CONFIG_PARAMS['canonical_exi_enabled'])

tools.save_code_to_file(config['filename'], code, parameters['folder'])
except KeyError as err:
Expand All @@ -131,7 +132,8 @@ def __generate_static_c(self, parameters):
generator = tools_generator.get_generator()
temp = generator.get_template(config['template'])
code = temp.render(filename=config['filename'], filekey=config['identifier'],
add_debug_code=self.__analyzer_data.add_debug_code_enabled)
add_debug_code=self.__analyzer_data.add_debug_code_enabled,
canonical_exi_enabled=tools_conf.CONFIG_PARAMS['canonical_exi_enabled'])

tools.save_code_to_file(config['filename'], code, parameters['folder'])
except KeyError as err:
Expand Down
63 changes: 53 additions & 10 deletions src/cbexigen/SchemaAnalyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@
from cbexigen.tools_config import CONFIG_PARAMS, get_config_module


_XSI_NAMESPACE = 'http://www.w3.org/2001/XMLSchema-instance'


def _canonical_attribute_sort_key(particle):
"""Sort key for canonical EXI attribute ordering per W3C Canonical EXI spec.

Order: xsi:type first, xsi:nil second, then lexicographic by (local_name, namespace_uri).
Uses Unicode code point comparison (Python's default string comparison).
"""
ns = getattr(particle, 'namespace_uri', '')
if ns == _XSI_NAMESPACE and particle.name == 'type':
return (0, '', '') # sort first
elif ns == _XSI_NAMESPACE and particle.name == 'nil':
return (1, '', '') # sort second
else:
return (2, particle.name, ns) # lexicographic by (local_name, ns_uri)


class SchemaAnalyzer(object):

def __init__(self, schema, schema_base, analyzer_data: AnalyzerData, schema_prefix):
Expand Down Expand Up @@ -336,6 +354,10 @@ def __get_particle_from_attribute(self, attribute: XsdAttribute):

particle.is_attribute = True

# Store namespace URI for canonical EXI attribute ordering.
# XsdAttribute inherits target_namespace from XsdComponent.
particle.namespace_uri = attribute.target_namespace or ''

if attribute.use.casefold() == 'required':
particle.min_occurs = 1
particle.max_occurs = 1
Expand Down Expand Up @@ -788,7 +810,10 @@ def __get_element_data(self, element: XsdElement, level, count, subst_list):
temp_list.append(self.__get_particle_from_attribute(attribute))

if len(temp_list) > 1:
temp_list.sort(key=lambda item: item.name, reverse=False)
if CONFIG_PARAMS.get('canonical_exi_enabled', 0) == 1:
temp_list.sort(key=_canonical_attribute_sort_key, reverse=False)
else:
temp_list.sort(key=lambda item: item.name, reverse=False)
element_data.particles.extend(temp_list)

if element.type.content_type_label == 'simple':
Expand Down Expand Up @@ -1080,20 +1105,38 @@ def __print_child_recursive(element_list, child_element: XsdElement):

# There are unused elements in the ISO-20 schema that are not yet included in the list of all elements
# for the fragment decoder and encoder. These elements can be determined via the components.
# Therefore, we iterate through the components of the schema and the 1st level of imports and complete the list.
# TODO: As only ISO-20 is currently affected and the only import of the individual schemas is the
# CommonTypes schema, recursive processing is not used here. This should be changed if necessary.
for component in self.__current_schema.iter_components():
if isinstance(component, Xsd11Element):
if component.name not in fragments.keys():
fragments[component.name] = __get_fragment(component)

for import_item in self.__current_schema.imports.values():
imported_schema = XMLSchema11(import_item.name, base_url=self.__schema_base, build=True)
for component in imported_schema.iter_components():
if isinstance(component, Xsd11Element):
if component.name not in fragments.keys():
fragments[component.name] = __get_fragment(component)
# Process schema imports to discover fragment elements.
# Canonical EXI requires recursive traversal of all transitive imports
# to match EXIficient's fragment grammar construction.
# Standard mode only processes direct (1st level) imports.
recursive = CONFIG_PARAMS.get('canonical_exi_enabled', 0) == 1

def process_imports(schema, processed_schemas=None):
if processed_schemas is None:
processed_schemas = set()

for import_item in schema.imports.values():
import_path = import_item.name
if import_path in processed_schemas:
continue
processed_schemas.add(import_path)

imported_schema = XMLSchema11(import_path, base_url=self.__schema_base, build=True)

for component in imported_schema.iter_components():
if isinstance(component, Xsd11Element):
if component.name not in fragments.keys():
fragments[component.name] = __get_fragment(component)

if recursive:
process_imports(imported_schema, processed_schemas)

process_imports(self.__current_schema)

# Sort the list of elements and types by 1. name and 2. namespace
sorted_by_name = dict(sorted(fragments.items(), key=lambda item: (item[1].name, item[1].namespace)))
Expand Down
4 changes: 4 additions & 0 deletions src/cbexigen/base_coder_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,10 @@ def append_to_element_grammars(self, grammar: ElementGrammar, element_typename):

def generate_element_grammars(self, element: ElementData):
self.reset_element_grammars()
# NOTE: Attribute particles are pre-sorted by SchemaAnalyzer.
# When canonical_exi_enabled=1, attributes are sorted by (local_name, namespace_uri)
# with xsi:type first and xsi:nil second, per W3C Canonical EXI spec.
# Child element particles remain in schema-defined order.
particle_is_part_of_sequence = False

# if the current element type is in the namespace elements dict,
Expand Down
41 changes: 41 additions & 0 deletions src/cbexigen/tools_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
CONFIG_PARAMS: Dict[str, Union[str, int]] = {
# add debug code while generating code
'add_debug_code': 0,
# enable canonical EXI code generation
'canonical_exi_enabled': 0,
# generate analysis tree while generating code
'generate_analysis_tree': 0,
'generate_analysis_tree_20': 0,
Expand Down Expand Up @@ -113,6 +115,45 @@ def process_config_parameters():
if hasattr(config_module, 'add_debug_code'):
CONFIG_PARAMS['add_debug_code'] = config_module.add_debug_code

# canonical EXI definitions
# canonical_exi_enabled
if hasattr(config_module, 'canonical_exi_enabled'):
CONFIG_PARAMS['canonical_exi_enabled'] = config_module.canonical_exi_enabled

# When canonical EXI is enabled, register the exi_types_encoder templates
# and add exi_types_encoder.h to all encoder include lists.
if CONFIG_PARAMS.get('canonical_exi_enabled', 0) == 1:
config_module.c_files_to_generate['exi_types_encoder'] = {
'prefix': '',
'type': 'static',
'folder': 'common',
'h': {
'template': 'static_code/exi_types_encoder.h.jinja',
'filename': 'exi_types_encoder.h',
'identifier': 'EXI_TYPES_ENCODER_H',
'include_std_lib': [],
'include_other': []
},
'c': {
'template': 'static_code/exi_types_encoder.c.jinja',
'filename': 'exi_types_encoder.c',
'identifier': 'EXI_TYPES_ENCODER_C',
'include_std_lib': [],
'include_other': []
}
}
# Add exi_types_encoder.h to all encoder include lists
for _, entry in config_module.c_files_to_generate.items():
if entry.get('type') == 'encoder':
c_config = entry.get('c', {})
includes = c_config.get('include_other', [])
if 'exi_types_encoder.h' not in includes:
if 'exi_basetypes_encoder.h' in includes:
idx = includes.index('exi_basetypes_encoder.h') + 1
else:
idx = 0
includes.insert(idx, 'exi_types_encoder.h')

''' analysis tree definitions '''
# generate_analysis_tree
if hasattr(config_module, 'generate_analysis_tree'):
Expand Down
7 changes: 7 additions & 0 deletions src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
# and create separate code for the debugging functions
add_debug_code = 0

# enable canonical EXI code generation
# this will add a canonical_mode field to exi_bitstream_t
# and enable Canonical EXI for Plug & Charge code paths in generated output
# mode=0 (EXI_MODE_STANDARD): schema-informed standard EXI (0x80 header, bit-packed)
# mode=1 (EXI_MODE_CANONICAL): Canonical EXI for Plug & Charge (0x80 header, bit-packed, type-aware)
canonical_exi_enabled = 0

# generate analysis tree while generating code
# this will generate an analysis tree file starting from the root element
# for the 15118-20 every message has its separate tree file
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{{ indent * level }}{{ decode_comment }}
{{ indent * level }}error = exi_basetypes_decoder_uint_16(stream, &{{ type_content_len }});
{{ indent * level }}if (error == 0)
{{ indent * level }}{
{{ indent * (level + 1) }}error = exi_basetypes_decoder_bytes(stream, {{ type_content_len }}, &{{ type_content }}[0], {{ type_define }});
{{ indent * (level + 1) }}error = exi_basetypes_decoder_uint_16(stream, &{{ type_content_len }});
{{ indent * (level + 1) }}if (error == 0)
{{ indent * (level + 1) }}{
{{ indent * (level + 2) }}error = exi_basetypes_decoder_bytes(stream, {{ type_content_len }}, &{{ type_content }}[0], {{ type_define }});
{{ indent * (level + 2) }}if (error == 0)
{{ indent * (level + 2) }}{
{%- if type_option == 1 %}
{{ indent * (level + 2) }}{{ type_value }}_isUsed = 1u;
{{ indent * (level + 3) }}{{ type_value }}_isUsed = 1u;
{%- endif %}
{{ indent * (level + 2) }}grammar_id = {{ next_grammar_id }};
{{ indent * (level + 3) }}grammar_id = {{ next_grammar_id }};
{{ indent * (level + 2) }}}
{{ indent * (level + 1) }}}
{{ indent * level }}}
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,8 @@ int exi_basetypes_encoder_bool(exi_bitstream_t* stream, int value)
stream->status_callback(EXI_DEBUG__BASETYPES_ENCODE_BOOL, 0, (int)value, 0);
}
{% endif %}
int error;
uint32_t bit = (value) ? 1 : 0;

error = exi_bitstream_write_bits(stream, 1, bit);

return error;
return exi_bitstream_write_bits(stream, 1, bit);
}

/*****************************************************************************
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ void exi_bitstream_init(exi_bitstream_t* stream, uint8_t* data, size_t data_size
stream->_flag_byte_pos = data_offset;

stream->status_callback = status_callback;
{%- if canonical_exi_enabled == 1 %}
stream->canonical_mode = 0;
{%- endif %}
{%- if add_debug_code == 1 %}

if (stream->status_callback)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ typedef struct exi_bitstream {

/* Pointer to callback for reporting errors or logging if assigned */
exi_status_callback status_callback;
{%- if canonical_exi_enabled == 1 %}
/* EXI encoding mode constants for canonical_mode field */
#define EXI_MODE_STANDARD 0u /* Schema-informed standard EXI (0x80 header, bit-packed) */
#define EXI_MODE_CANONICAL 1u /* Canonical EXI for Plug & Charge (0x80 header, bit-packed, type-aware) */

/* EXI encoding mode: EXI_MODE_STANDARD (0) or EXI_MODE_CANONICAL (1).
* Set by application code before calling encoder functions. */
uint8_t canonical_mode;
{%- endif %}
} exi_bitstream_t;


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@
#define EXI_ERROR__UNSUPPORTED_DATETIME_TYPE -211
#define EXI_ERROR__UNSUPPORTED_CHARACTER_VALUE -212

/* canonical decoding errors */
#define EXI_ERROR__INVALID_LEXICAL_VALUE -220
#define EXI_ERROR__NUMERIC_OVERFLOW -221

// fragment errors -230 to -259
#define EXI_ERROR__INCORRECT_END_FRAGMENT_VALUE -230

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#define EXI_SIMPLE_HEADER_VALUE 0x80



/**
* \brief Writes a simple EXI header (0x80)
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ int decode_exi_type_hex_binary(exi_bitstream_t* stream, uint16_t* value_len, uin
uint32_t eventCode;
int error;


error = exi_basetypes_decoder_nbit_uint(stream, 1, &eventCode);
if (error == 0)
{
Expand Down Expand Up @@ -53,6 +54,53 @@ int decode_exi_type_hex_binary(exi_bitstream_t* stream, uint16_t* value_len, uin
return error;
}

// *************
// Base64 Binary
// *************
int decode_exi_type_base64_binary(exi_bitstream_t* stream, uint16_t* value_len, uint8_t* value_buffer, size_t value_buffer_size)
{
uint32_t eventCode;
int error;

/* All modes: base64Binary decoded as raw bytes.
* Binary data uses native byte encoding per W3C EXI Section 7.1.9. */

error = exi_basetypes_decoder_nbit_uint(stream, 1, &eventCode);
if (error == 0)
{
if (eventCode == 0)
{
error = exi_basetypes_decoder_uint_16(stream, value_len);
if (error == 0)
{
error = exi_basetypes_decoder_bytes(stream, *value_len, value_buffer, value_buffer_size);
}
}
else
{
/* Second level event is not supported */
error = EXI_ERROR__UNSUPPORTED_SUB_EVENT;
}
}

/* if nothing went wrong, the error of last decoding is evaluated here */
if (error == 0)
{
/* test EE for simple element */
error = exi_basetypes_decoder_nbit_uint(stream, 1, &eventCode);
if (error == 0)
{
if (eventCode != 0)
{
/* deviants are not supported or also typecast and nillable */
error = EXI_ERROR__DEVIANTS_NOT_SUPPORTED;
}
}
}

return error;
}

// *********
// integers
// *********
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@
*/
int decode_exi_type_hex_binary(exi_bitstream_t* stream, uint16_t* value_len, uint8_t* value_buffer, size_t value_buffer_size);

/**
* \brief Decode base64Binary
*
* \param stream EXI bitstream
* \param value_len uint16_t (out) used length of decoded value
* \param value_buffer byte buffer (out) decoded value
* \param value_buffer_size size of the buffer
* \return Error-Code <> 0, if no error 0
*
*/
int decode_exi_type_base64_binary(exi_bitstream_t* stream, uint16_t* value_len, uint8_t* value_buffer, size_t value_buffer_size);

{% for size in [8, 16, 32, 64] -%}
/**
* \brief Decode {{ size }}-bit integer
Expand Down
Loading