Skip to content

Commit

Permalink
EncodeState: add an emplace_atomic_value() method analogous to `D…
Browse files Browse the repository at this point in the history
…ecodeState`'s `extract_atomic_value()`

this method converts an atomic value to bytes, emplaces the result at
the correct location, and updates the location where the `EncodeState`
object points to.

Signed-off-by: Andreas Lauser <[email protected]>
Signed-off-by: Katja Köhler <[email protected]>
  • Loading branch information
andlaus committed Apr 25, 2024
1 parent d1bf470 commit 9626268
Show file tree
Hide file tree
Showing 11 changed files with 152 additions and 149 deletions.
Binary file modified examples/somersault.pdx
Binary file not shown.
14 changes: 1 addition & 13 deletions odxtools/decodestate.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,6 @@
if TYPE_CHECKING:
from .tablerow import TableRow

# format specifiers for the data type using the bitstruct module
ODX_TYPE_TO_FORMAT_LETTER = {
DataType.A_INT32: "s",
DataType.A_UINT32: "u",
DataType.A_FLOAT32: "f",
DataType.A_FLOAT64: "f",
DataType.A_BYTEFIELD: "r",
DataType.A_UNICODE2STRING: "r", # UTF-16 strings must be converted explicitly
DataType.A_ASCIISTRING: "r",
DataType.A_UTF8STRING: "r",
}


@dataclass
class DecodeState:
Expand Down Expand Up @@ -94,7 +82,7 @@ def extract_atomic_value(

padding = (8 - (bit_length + self.cursor_bit_position) % 8) % 8
internal_value, = bitstruct.unpack_from(
f"{ODX_TYPE_TO_FORMAT_LETTER[base_data_type]}{bit_length}",
f"{base_data_type.bitstruct_format_letter}{bit_length}",
extracted_bytes,
offset=padding)

Expand Down
95 changes: 3 additions & 92 deletions odxtools/diagcodedtype.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
# SPDX-License-Identifier: MIT
import abc
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union, cast
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union

from .decodestate import ODX_TYPE_TO_FORMAT_LETTER, DecodeState
from .decodestate import DecodeState
from .encodestate import EncodeState
from .exceptions import EncodeError, odxassert, odxraise
from .exceptions import odxassert, odxraise
from .odxlink import OdxLinkDatabase, OdxLinkId
from .odxtypes import AtomicOdxType, DataType

try:
import bitstruct.c as bitstruct
except ImportError:
import bitstruct

if TYPE_CHECKING:
from .diaglayer import DiagLayer

Expand Down Expand Up @@ -56,90 +51,6 @@ def dct_type(self) -> DctType:
def is_highlow_byte_order(self) -> bool:
return self.is_highlow_byte_order_raw in [None, True]

@staticmethod
def _encode_internal_value(
*,
internal_value: AtomicOdxType,
bit_position: int,
bit_length: int,
base_data_type: DataType,
is_highlow_byte_order: bool,
) -> bytes:
"""Convert the internal_value to bytes and emplace this into the PDU"""
# Check that bytes and strings actually fit into the bit length
if base_data_type == DataType.A_BYTEFIELD:
if isinstance(internal_value, bytearray):
internal_value = bytes(internal_value)
if not isinstance(internal_value, bytes):
odxraise()
if 8 * len(internal_value) > bit_length:
raise EncodeError(f"The bytefield {internal_value.hex()} is too large "
f"({len(internal_value)} bytes)."
f" The maximum length is {bit_length//8}.")
if base_data_type == DataType.A_ASCIISTRING:
if not isinstance(internal_value, str):
odxraise()

# The spec says ASCII, meaning only byte values 0-127.
# But in practice, vendors use iso-8859-1, aka latin-1
# reason being iso-8859-1 never fails since it has a valid
# character mapping for every possible byte sequence.
internal_value = internal_value.encode("iso-8859-1")

if 8 * len(internal_value) > bit_length:
raise EncodeError(f"The string {repr(internal_value)} is too large."
f" The maximum number of characters is {bit_length//8}.")
elif base_data_type == DataType.A_UTF8STRING:
if not isinstance(internal_value, str):
odxraise()

internal_value = internal_value.encode("utf-8")

if 8 * len(internal_value) > bit_length:
raise EncodeError(f"The string {repr(internal_value)} is too large."
f" The maximum number of bytes is {bit_length//8}.")

elif base_data_type == DataType.A_UNICODE2STRING:
if not isinstance(internal_value, str):
odxraise()

text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le"
internal_value = internal_value.encode(text_encoding)

if 8 * len(internal_value) > bit_length:
raise EncodeError(f"The string {repr(internal_value)} is too large."
f" The maximum number of characters is {bit_length//16}.")

# If the bit length is zero, return empty bytes
if bit_length == 0:
if (base_data_type.value in [
DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
] and base_data_type.value != 0):
odxraise(
f"The number {repr(internal_value)} cannot be encoded into {bit_length} bits.",
EncodeError)
return b''

char = ODX_TYPE_TO_FORMAT_LETTER[base_data_type]
padding = (8 - ((bit_length + bit_position) % 8)) % 8
odxassert((0 <= padding and padding < 8 and (padding + bit_length + bit_position) % 8 == 0),
f"Incorrect padding {padding}")
left_pad = f"p{padding}" if padding > 0 else ""

# actually encode the value
coded = bitstruct.pack(f"{left_pad}{char}{bit_length}", internal_value)

# apply byte order for numeric objects
if not is_highlow_byte_order and base_data_type in [
DataType.A_INT32,
DataType.A_UINT32,
DataType.A_FLOAT32,
DataType.A_FLOAT64,
]:
coded = coded[::-1]

return cast(bytes, coded)

def _minimal_byte_length_of(self, internal_value: Union[bytes, str]) -> int:
"""Helper method to get the minimal byte length.
(needed for LeadingLength- and MinMaxLengthType)
Expand Down
2 changes: 1 addition & 1 deletion odxtools/dynamiclengthfield.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeSt

# ensure the correct message size if the field is empty
if len(physical_value) == 0:
encode_state.emplace_bytes(b"", "<padding>")
encode_state.emplace_bytes(b"")

# move cursor and origin positions
encode_state.origin_byte_position = orig_origin
Expand Down
114 changes: 106 additions & 8 deletions odxtools/encodestate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
from dataclasses import dataclass, field
from typing import Dict, Optional

from .exceptions import OdxWarning
from .exceptions import EncodeError, OdxWarning, odxassert, odxraise
from .odxtypes import AtomicOdxType, DataType

try:
import bitstruct.c as bitstruct
except ImportError:
import bitstruct


@dataclass
Expand Down Expand Up @@ -47,7 +53,92 @@ class EncodeState:
#: (needed for MinMaxLengthType, EndOfPduField, etc.)
is_end_of_pdu: bool = True

def emplace_bytes(self, new_data: bytes, param_name: Optional[str] = None) -> None:
def emplace_atomic_value(
self,
*,
internal_value: AtomicOdxType,
bit_length: int,
base_data_type: DataType,
is_highlow_byte_order: bool,
) -> None:
"""Convert the internal_value to bytes and emplace this into the PDU"""
# Check that bytes and strings actually fit into the bit length
if base_data_type == DataType.A_BYTEFIELD:
if isinstance(internal_value, bytearray):
internal_value = bytes(internal_value)
if not isinstance(internal_value, bytes):
odxraise()
if 8 * len(internal_value) > bit_length:
raise EncodeError(f"The bytefield {internal_value.hex()} is too large "
f"({len(internal_value)} bytes)."
f" The maximum length is {bit_length//8}.")
if base_data_type == DataType.A_ASCIISTRING:
if not isinstance(internal_value, str):
odxraise()

# The spec says ASCII, meaning only byte values 0-127.
# But in practice, vendors use iso-8859-1, aka latin-1
# reason being iso-8859-1 never fails since it has a valid
# character mapping for every possible byte sequence.
internal_value = internal_value.encode("iso-8859-1")

if 8 * len(internal_value) > bit_length:
raise EncodeError(f"The string {repr(internal_value)} is too large."
f" The maximum number of characters is {bit_length//8}.")
elif base_data_type == DataType.A_UTF8STRING:
if not isinstance(internal_value, str):
odxraise()

internal_value = internal_value.encode("utf-8")

if 8 * len(internal_value) > bit_length:
raise EncodeError(f"The string {repr(internal_value)} is too large."
f" The maximum number of bytes is {bit_length//8}.")

elif base_data_type == DataType.A_UNICODE2STRING:
if not isinstance(internal_value, str):
odxraise()

text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le"
internal_value = internal_value.encode(text_encoding)

if 8 * len(internal_value) > bit_length:
raise EncodeError(f"The string {repr(internal_value)} is too large."
f" The maximum number of characters is {bit_length//16}.")

# If the bit length is zero, return empty bytes
if bit_length == 0:
if (base_data_type.value in [
DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
] and base_data_type.value != 0):
odxraise(
f"The number {repr(internal_value)} cannot be encoded into {bit_length} bits.",
EncodeError)
self.emplace_bytes(b'')
return

char = base_data_type.bitstruct_format_letter
padding = (8 - ((bit_length + self.cursor_bit_position) % 8)) % 8
odxassert((0 <= padding and padding < 8 and
(padding + bit_length + self.cursor_bit_position) % 8 == 0),
f"Incorrect padding {padding}")
left_pad = f"p{padding}" if padding > 0 else ""

# actually encode the value
coded = bitstruct.pack(f"{left_pad}{char}{bit_length}", internal_value)

# apply byte order for numeric objects
if not is_highlow_byte_order and base_data_type in [
DataType.A_INT32,
DataType.A_UINT32,
DataType.A_FLOAT32,
DataType.A_FLOAT64,
]:
coded = coded[::-1]

self.emplace_bytes(coded)

def emplace_bytes(self, new_data: bytes, obj_name: Optional[str] = None) -> None:
pos = self.cursor_byte_position

# Make blob longer if necessary
Expand All @@ -60,12 +151,19 @@ def emplace_bytes(self, new_data: bytes, param_name: Optional[str] = None) -> No
# the value to be inserted is bitwise "disjoint" from the
# value which is already in the PDU...
if self.coded_message[pos + i] & new_data[i] != 0:
param_name = "<UNKNOWN>" if param_name is None else param_name
warnings.warn(
f"Object '{param_name}' overlaps with another parameter (bits are already set)",
OdxWarning,
stacklevel=1,
)
if obj_name is not None:
warnings.warn(
f"'{obj_name}' overlaps with another object (bits to be set are already set)",
OdxWarning,
stacklevel=1,
)
else:
warnings.warn(
"Object overlap (bits to be set are already set)",
OdxWarning,
stacklevel=1,
)

self.coded_message[pos + i] |= new_data[i]

self.cursor_byte_position += len(new_data)
Expand Down
8 changes: 2 additions & 6 deletions odxtools/leadinglengthinfotype.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,20 @@ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeSta

byte_length = self._minimal_byte_length_of(internal_value)

length_bytes = self._encode_internal_value(
encode_state.emplace_atomic_value(
internal_value=byte_length,
bit_position=encode_state.cursor_bit_position,
bit_length=self.bit_length,
base_data_type=DataType.A_UINT32,
is_highlow_byte_order=self.is_highlow_byte_order,
)

value_bytes = self._encode_internal_value(
encode_state.emplace_atomic_value(
internal_value=internal_value,
bit_position=0,
bit_length=8 * byte_length,
base_data_type=self.base_data_type,
is_highlow_byte_order=self.is_highlow_byte_order,
)

encode_state.emplace_bytes(length_bytes + value_bytes, "<LEADING-LENGTH-INFO-TYPE>")

@override
def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:

Expand Down
35 changes: 17 additions & 18 deletions odxtools/minmaxlengthtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,23 @@ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeSta
else:
data_length = len(internal_value)

value_bytes = bytearray(
self._encode_internal_value(
internal_value=internal_value,
bit_position=0,
bit_length=8 * data_length,
base_data_type=self.base_data_type,
is_highlow_byte_order=self.is_highlow_byte_order,
))
orig_cursor = encode_state.cursor_byte_position
encode_state.emplace_atomic_value(
internal_value=internal_value,
bit_length=8 * data_length,
base_data_type=self.base_data_type,
is_highlow_byte_order=self.is_highlow_byte_order,
)
value_len = encode_state.cursor_byte_position - orig_cursor

# TODO: ensure that the termination delimiter is not
# encountered within the encoded value.

odxassert(
self.termination != "END-OF-PDU" or encode_state.is_end_of_pdu,
"Encountered a MIN-MAX-LENGTH type with END-OF-PDU termination "
"which is not at the end of the PDU")
if encode_state.is_end_of_pdu or len(value_bytes) == self.max_length:
"which is not located at the end of the PDU")
if encode_state.is_end_of_pdu or value_len == self.max_length:
# All termination types may be ended by the end of the PDU
# or once reaching the maximum length. In this case, we
# must not add the termination sequence
Expand All @@ -91,25 +91,24 @@ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeSta

# ensure that we don't try to encode an odd-length
# value when using a two-byte terminator
odxassert(len(value_bytes) % len(termination_sequence) == 0)
odxassert(value_len % len(termination_sequence) == 0)

value_bytes.extend(termination_sequence)
value_len += len(termination_sequence)
encode_state.emplace_bytes(termination_sequence)

if len(value_bytes) < self.min_length:
if value_len < self.min_length:
odxraise(
f"Encoded value for MinMaxLengthType "
f"must be at least {self.min_length} bytes long. "
f"(Is: {len(value_bytes)} bytes.)", EncodeError)
f"(Is: {value_len} bytes.)", EncodeError)
return
elif self.max_length is not None and len(value_bytes) > self.max_length:
elif self.max_length is not None and value_len > self.max_length:
odxraise(
f"Encoded value for MinMaxLengthType "
f"must not be longer than {self.max_length} bytes. "
f"(Is: {len(value_bytes)} bytes.)", EncodeError)
f"(Is: {value_len} bytes.)", EncodeError)
return

encode_state.emplace_bytes(value_bytes, "<MIN-MAX-LENGTH-TYPE>")

def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
odxassert(decode_state.cursor_bit_position == 0,
"No bit position can be specified for MIN-MAX-LENGTH-TYPE values.")
Expand Down
Loading

0 comments on commit 9626268

Please sign in to comment.