pyxcp 0.23.3__cp312-cp312-win_arm64.whl → 0.25.6__cp312-cp312-win_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyxcp/__init__.py +1 -1
- pyxcp/asamkeydll.exe +0 -0
- pyxcp/cmdline.py +15 -30
- pyxcp/config/__init__.py +73 -20
- pyxcp/cpp_ext/aligned_buffer.hpp +168 -0
- pyxcp/cpp_ext/bin.hpp +7 -6
- pyxcp/cpp_ext/cpp_ext.cp310-win_arm64.pyd +0 -0
- pyxcp/cpp_ext/cpp_ext.cp311-win_arm64.pyd +0 -0
- pyxcp/cpp_ext/cpp_ext.cp312-win_arm64.pyd +0 -0
- pyxcp/cpp_ext/daqlist.hpp +241 -73
- pyxcp/cpp_ext/extension_wrapper.cpp +123 -15
- pyxcp/cpp_ext/framing.hpp +360 -0
- pyxcp/cpp_ext/mcobject.hpp +5 -3
- pyxcp/cpp_ext/sxi_framing.hpp +332 -0
- pyxcp/daq_stim/__init__.py +182 -45
- pyxcp/daq_stim/optimize/binpacking.py +2 -2
- pyxcp/daq_stim/scheduler.cpp +8 -8
- pyxcp/daq_stim/stim.cp310-win_arm64.pyd +0 -0
- pyxcp/daq_stim/stim.cp311-win_arm64.pyd +0 -0
- pyxcp/daq_stim/stim.cp312-win_arm64.pyd +0 -0
- pyxcp/errormatrix.py +2 -2
- pyxcp/examples/run_daq.py +5 -3
- pyxcp/examples/xcp_policy.py +6 -6
- pyxcp/examples/xcp_read_benchmark.py +2 -2
- pyxcp/examples/xcp_skel.py +1 -2
- pyxcp/examples/xcp_unlock.py +10 -12
- pyxcp/examples/xcp_user_supplied_driver.py +1 -2
- pyxcp/examples/xcphello.py +2 -15
- pyxcp/examples/xcphello_recorder.py +2 -2
- pyxcp/master/__init__.py +1 -0
- pyxcp/master/errorhandler.py +248 -13
- pyxcp/master/master.py +838 -250
- pyxcp/recorder/.idea/.gitignore +8 -0
- pyxcp/recorder/.idea/misc.xml +4 -0
- pyxcp/recorder/.idea/modules.xml +8 -0
- pyxcp/recorder/.idea/recorder.iml +6 -0
- pyxcp/recorder/.idea/sonarlint/issuestore/3/8/3808afc69ac1edb9d760000a2f137335b1b99728 +7 -0
- pyxcp/recorder/.idea/sonarlint/issuestore/9/a/9a2aa4db38d3115ed60da621e012c0efc0172aae +0 -0
- pyxcp/recorder/.idea/sonarlint/issuestore/b/4/b49006702b459496a8e8c94ebe60947108361b91 +0 -0
- pyxcp/recorder/.idea/sonarlint/issuestore/index.pb +7 -0
- pyxcp/recorder/.idea/sonarlint/securityhotspotstore/3/8/3808afc69ac1edb9d760000a2f137335b1b99728 +0 -0
- pyxcp/recorder/.idea/sonarlint/securityhotspotstore/9/a/9a2aa4db38d3115ed60da621e012c0efc0172aae +0 -0
- pyxcp/recorder/.idea/sonarlint/securityhotspotstore/b/4/b49006702b459496a8e8c94ebe60947108361b91 +0 -0
- pyxcp/recorder/.idea/sonarlint/securityhotspotstore/index.pb +7 -0
- pyxcp/recorder/.idea/vcs.xml +10 -0
- pyxcp/recorder/__init__.py +5 -10
- pyxcp/recorder/converter/__init__.py +4 -10
- pyxcp/recorder/reader.hpp +0 -1
- pyxcp/recorder/reco.py +1 -0
- pyxcp/recorder/rekorder.cp310-win_arm64.pyd +0 -0
- pyxcp/recorder/rekorder.cp311-win_arm64.pyd +0 -0
- pyxcp/recorder/rekorder.cp312-win_arm64.pyd +0 -0
- pyxcp/recorder/unfolder.hpp +129 -107
- pyxcp/recorder/wrap.cpp +3 -8
- pyxcp/scripts/xcp_fetch_a2l.py +2 -2
- pyxcp/scripts/xcp_id_scanner.py +1 -2
- pyxcp/scripts/xcp_info.py +66 -51
- pyxcp/scripts/xcp_profile.py +1 -2
- pyxcp/tests/test_daq.py +1 -1
- pyxcp/tests/test_framing.py +262 -0
- pyxcp/tests/test_master.py +210 -100
- pyxcp/tests/test_transport.py +138 -42
- pyxcp/timing.py +1 -1
- pyxcp/transport/__init__.py +8 -5
- pyxcp/transport/base.py +187 -143
- pyxcp/transport/can.py +117 -13
- pyxcp/transport/eth.py +55 -20
- pyxcp/transport/hdf5_policy.py +167 -0
- pyxcp/transport/sxi.py +126 -52
- pyxcp/transport/transport_ext.cp310-win_arm64.pyd +0 -0
- pyxcp/transport/transport_ext.cp311-win_arm64.pyd +0 -0
- pyxcp/transport/transport_ext.cp312-win_arm64.pyd +0 -0
- pyxcp/transport/transport_ext.hpp +214 -0
- pyxcp/transport/transport_wrapper.cpp +249 -0
- pyxcp/transport/usb_transport.py +47 -31
- pyxcp/types.py +0 -13
- pyxcp/{utils.py → utils/__init__.py} +3 -4
- pyxcp/utils/cli.py +78 -0
- pyxcp-0.25.6.dist-info/METADATA +341 -0
- pyxcp-0.25.6.dist-info/RECORD +153 -0
- {pyxcp-0.23.3.dist-info → pyxcp-0.25.6.dist-info}/WHEEL +1 -1
- pyxcp/examples/conf_sxi.json +0 -9
- pyxcp/examples/conf_sxi.toml +0 -7
- pyxcp-0.23.3.dist-info/METADATA +0 -219
- pyxcp-0.23.3.dist-info/RECORD +0 -131
- {pyxcp-0.23.3.dist-info → pyxcp-0.25.6.dist-info}/entry_points.txt +0 -0
- {pyxcp-0.23.3.dist-info → pyxcp-0.25.6.dist-info/licenses}/LICENSE +0 -0
pyxcp/__init__.py
CHANGED
pyxcp/asamkeydll.exe
ADDED
|
Binary file
|
pyxcp/cmdline.py
CHANGED
|
@@ -4,9 +4,8 @@ Parse (transport-layer specific) command line parameters
|
|
|
4
4
|
and create a XCP master instance.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import argparse
|
|
7
8
|
import warnings
|
|
8
|
-
from dataclasses import dataclass
|
|
9
|
-
from typing import List
|
|
10
9
|
|
|
11
10
|
from pyxcp.config import ( # noqa: F401
|
|
12
11
|
create_application,
|
|
@@ -14,33 +13,12 @@ from pyxcp.config import ( # noqa: F401
|
|
|
14
13
|
reset_application,
|
|
15
14
|
)
|
|
16
15
|
from pyxcp.master import Master
|
|
16
|
+
from pyxcp.utils.cli import StrippingParser
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
warnings.simplefilter("always")
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
@dataclass
|
|
23
|
-
class Option:
|
|
24
|
-
short_opt: str
|
|
25
|
-
long_opt: str = ""
|
|
26
|
-
dest: str = ""
|
|
27
|
-
help: str = ""
|
|
28
|
-
type: str = ""
|
|
29
|
-
default: str = ""
|
|
30
|
-
action: str = ""
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class FakeParser:
|
|
34
|
-
"""Parser that collects arguments for later processing."""
|
|
35
|
-
|
|
36
|
-
def __init__(self):
|
|
37
|
-
self.options = []
|
|
38
|
-
|
|
39
|
-
def add_argument(self, short_opt, long_opt="", dest="", help="", type=None, default=None, action=None):
|
|
40
|
-
"""Collect argument definitions without issuing warnings."""
|
|
41
|
-
self.options.append(Option(short_opt, long_opt, dest, help, type, default, action))
|
|
42
|
-
|
|
43
|
-
|
|
44
22
|
class ArgumentParser:
|
|
45
23
|
"""Argument parser for pyXCP applications.
|
|
46
24
|
|
|
@@ -49,13 +27,20 @@ class ArgumentParser:
|
|
|
49
27
|
the parsed arguments.
|
|
50
28
|
"""
|
|
51
29
|
|
|
52
|
-
def __init__(self,
|
|
53
|
-
|
|
54
|
-
|
|
30
|
+
def __init__(self, user_parser=None, description=None, *args, **kws):
|
|
31
|
+
if isinstance(user_parser, argparse.ArgumentParser):
|
|
32
|
+
self._parser = StrippingParser(user_parser)
|
|
33
|
+
self._callout = None
|
|
34
|
+
else:
|
|
35
|
+
# Create a default parser. user_parser might be a callout function or None.
|
|
36
|
+
parser = argparse.ArgumentParser(description=description)
|
|
37
|
+
self._parser = StrippingParser(parser)
|
|
38
|
+
self._callout = user_parser
|
|
55
39
|
self._description = description
|
|
40
|
+
self.args = self._parser.parse_and_strip()
|
|
56
41
|
|
|
57
42
|
def run(self, policy=None, transport_layer_interface=None):
|
|
58
|
-
"""Create and configure a master instance.
|
|
43
|
+
"""Create and configure a synchronous master instance.
|
|
59
44
|
|
|
60
45
|
Args:
|
|
61
46
|
policy: Optional policy to use for the master
|
|
@@ -65,7 +50,7 @@ class ArgumentParser:
|
|
|
65
50
|
A configured master instance
|
|
66
51
|
"""
|
|
67
52
|
# Create the application with custom arguments and callout
|
|
68
|
-
application = get_application(
|
|
53
|
+
application = get_application(options=[], callout=self._callout)
|
|
69
54
|
|
|
70
55
|
# Create the master instance
|
|
71
56
|
master = Master(
|
|
@@ -81,4 +66,4 @@ class ArgumentParser:
|
|
|
81
66
|
|
|
82
67
|
@property
|
|
83
68
|
def parser(self):
|
|
84
|
-
return self._parser
|
|
69
|
+
return self._parser.parser
|
pyxcp/config/__init__.py
CHANGED
|
@@ -913,6 +913,9 @@ class General(Configurable):
|
|
|
913
913
|
connect_retries = Integer(help="Number of CONNECT retries (None for infinite retries).", allow_none=True, default_value=3).tag(
|
|
914
914
|
config=True
|
|
915
915
|
)
|
|
916
|
+
# Structured diagnostics dump options
|
|
917
|
+
diagnostics_on_failure = Bool(True, help="Append a structured diagnostics dump to timeout errors.").tag(config=True)
|
|
918
|
+
diagnostics_last_pdus = Integer(20, help="How many recent PDUs to include in diagnostics dump.").tag(config=True)
|
|
916
919
|
seed_n_key_dll = Unicode("", allow_none=False, help="Dynamic library used for slave resource unlocking.").tag(config=True)
|
|
917
920
|
seed_n_key_dll_same_bit_width = Bool(False, help="").tag(config=True)
|
|
918
921
|
custom_dll_loader = Unicode(allow_none=True, default_value=None, help="Use an custom seed and key DLL loader.").tag(config=True)
|
|
@@ -1010,6 +1013,11 @@ class PyXCP(Application):
|
|
|
1010
1013
|
config=True
|
|
1011
1014
|
)
|
|
1012
1015
|
|
|
1016
|
+
# Logging options
|
|
1017
|
+
structured_logging = Bool(False, help="Emit one-line JSON logs instead of rich text.").tag(config=True)
|
|
1018
|
+
# Use log_output_format to avoid clashing with traitlets.Application.log_format (a %-style template)
|
|
1019
|
+
log_output_format = Enum(values=["rich", "json"], default_value="rich", help="Select logging output format.").tag(config=True)
|
|
1020
|
+
|
|
1013
1021
|
classes = List([General, Transport, CustomArgs])
|
|
1014
1022
|
|
|
1015
1023
|
subcommands = dict(
|
|
@@ -1026,34 +1034,79 @@ class PyXCP(Application):
|
|
|
1026
1034
|
self.subapp.start()
|
|
1027
1035
|
exit(2)
|
|
1028
1036
|
else:
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
self.
|
|
1037
|
+
# Always read configuration and then set up our logger explicitly to avoid
|
|
1038
|
+
# traitlets.Application default logging using an incompatible 'log_format'.
|
|
1039
|
+
self._read_configuration(self.config_file)
|
|
1040
|
+
try:
|
|
1041
|
+
# Ensure base Application.log_format is a valid %-style template
|
|
1042
|
+
# (Users might set c.PyXCP.log_format = "json" which clashes with traitlets behavior.)
|
|
1043
|
+
self.log_format = "%(message)s" # type: ignore[assignment]
|
|
1044
|
+
except Exception:
|
|
1045
|
+
pass
|
|
1046
|
+
self._setup_logger()
|
|
1036
1047
|
self.log.debug(f"pyxcp version: {self.version}")
|
|
1037
1048
|
|
|
1038
1049
|
def _setup_logger(self):
|
|
1039
1050
|
from pyxcp.types import Command
|
|
1040
1051
|
|
|
1041
1052
|
# Remove any handlers installed by `traitlets`.
|
|
1042
|
-
for hdl in self.log.handlers:
|
|
1053
|
+
for hdl in list(self.log.handlers):
|
|
1043
1054
|
self.log.removeHandler(hdl)
|
|
1044
1055
|
|
|
1045
|
-
# formatter
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1056
|
+
# Decide formatter/handler based on config
|
|
1057
|
+
use_json = False
|
|
1058
|
+
try:
|
|
1059
|
+
# Prefer explicit log_output_format; fallback to structured_logging for compatibility
|
|
1060
|
+
use_json = getattr(self, "log_output_format", "rich") == "json" or getattr(self, "structured_logging", False)
|
|
1061
|
+
# Backward-compat: if someone set PyXCP.log_format="json" in config, honor it here too
|
|
1062
|
+
if not use_json:
|
|
1063
|
+
lf = getattr(self, "log_format", None)
|
|
1064
|
+
if isinstance(lf, str) and lf.lower() == "json":
|
|
1065
|
+
use_json = True
|
|
1066
|
+
except Exception:
|
|
1067
|
+
use_json = False
|
|
1068
|
+
|
|
1069
|
+
if use_json:
|
|
1070
|
+
|
|
1071
|
+
class JSONFormatter(logging.Formatter):
|
|
1072
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
1073
|
+
# Build a minimal structured payload
|
|
1074
|
+
payload = {
|
|
1075
|
+
"time": self.formatTime(record, self.datefmt),
|
|
1076
|
+
"level": record.levelname,
|
|
1077
|
+
"logger": record.name,
|
|
1078
|
+
"message": record.getMessage(),
|
|
1079
|
+
}
|
|
1080
|
+
# Include extras if present
|
|
1081
|
+
for key in ("transport", "host", "port", "protocol", "event", "command"):
|
|
1082
|
+
if hasattr(record, key):
|
|
1083
|
+
payload[key] = getattr(record, key)
|
|
1084
|
+
# Exceptions
|
|
1085
|
+
if record.exc_info:
|
|
1086
|
+
payload["exc_type"] = record.exc_info[0].__name__ if record.exc_info[0] else None
|
|
1087
|
+
payload["exc_text"] = self.formatException(record.exc_info)
|
|
1088
|
+
try:
|
|
1089
|
+
import json as _json
|
|
1090
|
+
|
|
1091
|
+
return _json.dumps(payload, ensure_ascii=False)
|
|
1092
|
+
except Exception:
|
|
1093
|
+
return f"{payload}"
|
|
1094
|
+
|
|
1095
|
+
handler = logging.StreamHandler()
|
|
1096
|
+
formatter = JSONFormatter(datefmt=self.log_datefmt)
|
|
1097
|
+
handler.setFormatter(formatter)
|
|
1098
|
+
handler.setLevel(self.log_level)
|
|
1099
|
+
self.log.addHandler(handler)
|
|
1100
|
+
else:
|
|
1101
|
+
keywords = list(Command.__members__.keys()) + ["ARGS", "KWS"] # Syntax highlight XCP commands and other stuff.
|
|
1102
|
+
rich_handler = RichHandler(
|
|
1103
|
+
rich_tracebacks=True,
|
|
1104
|
+
tracebacks_show_locals=True,
|
|
1105
|
+
log_time_format=self.log_datefmt,
|
|
1106
|
+
level=self.log_level,
|
|
1107
|
+
keywords=keywords,
|
|
1108
|
+
)
|
|
1109
|
+
self.log.addHandler(rich_handler)
|
|
1057
1110
|
|
|
1058
1111
|
def initialize(self, argv=None):
|
|
1059
1112
|
from pyxcp import __version__ as pyxcp_version
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
|
|
2
|
+
#if !defined(__ALIGNED_BUFFER_HPP)
|
|
3
|
+
#define __ALIGNED_BUFFER_HPP
|
|
4
|
+
|
|
5
|
+
#include <variant>
|
|
6
|
+
|
|
7
|
+
#include <cstdint>
|
|
8
|
+
#include <cstdlib>
|
|
9
|
+
#include <vector>
|
|
10
|
+
#include <string_view>
|
|
11
|
+
#include <stdexcept>
|
|
12
|
+
#include <algorithm>
|
|
13
|
+
#include <cstring>
|
|
14
|
+
|
|
15
|
+
#include <pybind11/pybind11.h>
|
|
16
|
+
|
|
17
|
+
#if (defined(_WIN32) || defined(_WIN64)) && defined(_MSC_VER)
|
|
18
|
+
#include <malloc.h>
|
|
19
|
+
#endif
|
|
20
|
+
|
|
21
|
+
namespace py = pybind11;
|
|
22
|
+
|
|
23
|
+
class AlignedBuffer {
|
|
24
|
+
|
|
25
|
+
public:
|
|
26
|
+
|
|
27
|
+
AlignedBuffer(size_t size = 0xffff) : m_size(size), m_current_pos(0) {
|
|
28
|
+
m_buffer = nullptr;
|
|
29
|
+
// Create naturally aligned buffer.
|
|
30
|
+
constexpr std::size_t align = alignof(int);
|
|
31
|
+
// aligned_alloc requires size to be a multiple of alignment.
|
|
32
|
+
const std::size_t aligned_size = ((m_size + align - 1) / align) * align;
|
|
33
|
+
#if (defined(_WIN32) || defined(_WIN64)) && defined(_MSC_VER)
|
|
34
|
+
m_buffer = static_cast<uint8_t*>(::_aligned_malloc(aligned_size, align));
|
|
35
|
+
#else
|
|
36
|
+
m_buffer = static_cast<uint8_t*>(::aligned_alloc(align, aligned_size));
|
|
37
|
+
#endif
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
AlignedBuffer(const AlignedBuffer& other) = delete;
|
|
41
|
+
AlignedBuffer& operator=(const AlignedBuffer& other) = delete;
|
|
42
|
+
AlignedBuffer(AlignedBuffer&& other) = delete;
|
|
43
|
+
AlignedBuffer& operator=(AlignedBuffer&& other) = delete;
|
|
44
|
+
|
|
45
|
+
~AlignedBuffer() {
|
|
46
|
+
if (m_buffer) {
|
|
47
|
+
#if (defined(_WIN32) || defined(_WIN64)) && defined(_MSC_VER)
|
|
48
|
+
::_aligned_free(m_buffer);
|
|
49
|
+
#else
|
|
50
|
+
::free(m_buffer);
|
|
51
|
+
#endif
|
|
52
|
+
m_buffer = nullptr;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
void reset() noexcept {
|
|
57
|
+
m_current_pos = 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
std::size_t size() const noexcept {
|
|
61
|
+
return m_current_pos;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Get an element by index
|
|
65
|
+
uint8_t get(size_t index) const {
|
|
66
|
+
if (index >= size()) throw std::out_of_range("Index out of range");
|
|
67
|
+
return m_buffer[index];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
void append(uint8_t value) {
|
|
71
|
+
if ((m_current_pos + 1) > m_size) {
|
|
72
|
+
throw std::overflow_error("Buffer overflow");
|
|
73
|
+
}
|
|
74
|
+
m_buffer[m_current_pos] = value;
|
|
75
|
+
m_current_pos++;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Set an element by index
|
|
79
|
+
void set(size_t index, uint8_t value) {
|
|
80
|
+
if (index >= size()) throw std::out_of_range("Index out of range");
|
|
81
|
+
m_buffer[index] = value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static std::string_view bytes_as_string_view(const py::bytes& data) {
|
|
85
|
+
char* buf = nullptr;
|
|
86
|
+
Py_ssize_t len = 0;
|
|
87
|
+
if (PyBytes_AsStringAndSize(data.ptr(), &buf, &len) != 0 || buf == nullptr || len < 0) {
|
|
88
|
+
return std::string_view{};
|
|
89
|
+
}
|
|
90
|
+
return std::string_view(buf, static_cast<std::size_t>(len));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
void extend(const py::bytes& values) {
|
|
95
|
+
auto data_view = bytes_as_string_view(values);
|
|
96
|
+
|
|
97
|
+
if ((data_view.size() + m_current_pos) > m_size) {
|
|
98
|
+
throw std::invalid_argument("Values vector is too large");
|
|
99
|
+
}
|
|
100
|
+
if (!data_view.empty()) {
|
|
101
|
+
std::memcpy(m_buffer + m_current_pos, data_view.data(), data_view.size());
|
|
102
|
+
m_current_pos += data_view.size();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
void extend(const std::vector<std::uint8_t>& values) {
|
|
107
|
+
if ((values.size() + m_current_pos) > m_size) {
|
|
108
|
+
throw std::invalid_argument("Values vector is too large");
|
|
109
|
+
}
|
|
110
|
+
if (!values.empty()) {
|
|
111
|
+
std::memcpy(m_buffer + m_current_pos, values.data(), values.size());
|
|
112
|
+
m_current_pos += values.size();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
std::variant<uint8_t, py::bytes> get_item(py::object index) const {
|
|
117
|
+
if (py::isinstance<py::slice>(index)) {
|
|
118
|
+
py::slice slice = index.cast<py::slice>();
|
|
119
|
+
size_t start, stop, step, length;
|
|
120
|
+
if (!slice.compute(size(), &start, &stop, &step, &length)) {
|
|
121
|
+
throw py::error_already_set();
|
|
122
|
+
}
|
|
123
|
+
return slice(start, stop, step);
|
|
124
|
+
} else if (py::isinstance<py::int_>(index)) {
|
|
125
|
+
Py_ssize_t idx = index.cast<Py_ssize_t>();
|
|
126
|
+
if (idx < 0) {
|
|
127
|
+
idx += static_cast<Py_ssize_t>(size());
|
|
128
|
+
}
|
|
129
|
+
if (idx < 0 || static_cast<std::size_t>(idx) >= size()) {
|
|
130
|
+
throw std::out_of_range("Index out of range");
|
|
131
|
+
}
|
|
132
|
+
return get(static_cast<std::size_t>(idx));
|
|
133
|
+
} else {
|
|
134
|
+
throw py::type_error("Invalid index type");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
py::bytes slice(size_t start, size_t stop, size_t step) const {
|
|
139
|
+
if (step == 0) {
|
|
140
|
+
throw std::invalid_argument("Step cannot be zero");
|
|
141
|
+
}
|
|
142
|
+
// Clamp indices to valid range
|
|
143
|
+
start = std::max(size_t(0), std::min(start, size_t(size())));
|
|
144
|
+
stop = std::max(size_t(0), std::min(stop, size_t(size())));
|
|
145
|
+
|
|
146
|
+
if (start >= stop) {
|
|
147
|
+
return py::bytes("");
|
|
148
|
+
}
|
|
149
|
+
if (step == 1) {
|
|
150
|
+
return py::bytes(reinterpret_cast<const char*>(m_buffer) + start, stop - start);
|
|
151
|
+
}
|
|
152
|
+
// General step handling (build result with stride)
|
|
153
|
+
std::string out;
|
|
154
|
+
out.reserve((stop - start + step - 1) / step);
|
|
155
|
+
for (size_t i = start; i < stop; i += step) {
|
|
156
|
+
out.push_back(static_cast<char>(m_buffer[i]));
|
|
157
|
+
}
|
|
158
|
+
return py::bytes(out);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private:
|
|
162
|
+
size_t m_size;
|
|
163
|
+
size_t m_current_pos;
|
|
164
|
+
uint8_t * m_buffer;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
#endif // __ALIGNED_BUFFER_HPP
|
pyxcp/cpp_ext/bin.hpp
CHANGED
|
@@ -79,17 +79,18 @@ std::string bin_entries_to_string(const std::vector<McObject>& entries);
|
|
|
79
79
|
|
|
80
80
|
std::string to_string(const Bin& obj) {
|
|
81
81
|
std::stringstream ss;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
<< "])";
|
|
82
|
+
ss << "Bin(size=" << obj.get_size() << ", residual_capacity=" << obj.get_residual_capacity() << ", entries=["
|
|
83
|
+
<< bin_entries_to_string(obj.get_entries()) << "])";
|
|
85
84
|
return ss.str();
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
std::string bin_entries_to_string(const std::vector<McObject>& entries) {
|
|
89
88
|
std::stringstream ss;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
for (std::size_t i = 0; i < entries.size(); ++i) {
|
|
90
|
+
ss << to_string(entries[i]);
|
|
91
|
+
if (i + 1 < entries.size()) {
|
|
92
|
+
ss << ", ";
|
|
93
|
+
}
|
|
93
94
|
}
|
|
94
95
|
return ss.str();
|
|
95
96
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|