ltcodecs 4.3.0__tar.gz → 4.4.0__tar.gz

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.
Files changed (71) hide show
  1. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/.gitignore +2 -0
  2. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/.gitlab-ci.yml +8 -5
  3. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/PKG-INFO +1 -1
  4. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/docs/codec_configs.rst +87 -2
  5. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/docs/reference/codec_aliases.rst +4 -0
  6. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/docs/reference/codecs.rst +16 -2
  7. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/docs/reference/config_schema.rst +95 -1
  8. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/docs/reference/python_api.rst +16 -0
  9. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/docs/ros_messages.rst +38 -0
  10. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/pyproject.toml +2 -2
  11. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/__init__.py +6 -0
  12. ltcodecs-4.4.0/src/ltcodecs/_length_modes.py +61 -0
  13. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/bytes_codec.py +27 -8
  14. ltcodecs-4.4.0/src/ltcodecs/constant_codec.py +41 -0
  15. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/dict_field_codec.py +37 -17
  16. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/field_codec.py +9 -0
  17. ltcodecs-4.4.0/src/ltcodecs/inspect_cli.py +69 -0
  18. ltcodecs-4.4.0/src/ltcodecs/inspection.py +363 -0
  19. ltcodecs-4.4.0/src/ltcodecs/metadata_field_codec.py +51 -0
  20. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/optional_field_codec.py +4 -1
  21. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/padding_codec.py +2 -0
  22. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/ros_msg_field_codec.py +50 -19
  23. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/string_codecs.py +41 -13
  24. ltcodecs-4.4.0/src/ltcodecs/variable_len_array_codec.py +199 -0
  25. ltcodecs-4.4.0/src/ltcodecs/variable_len_bit_array_codec.py +117 -0
  26. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs.egg-info/PKG-INFO +1 -1
  27. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs.egg-info/SOURCES.txt +9 -1
  28. ltcodecs-4.4.0/src/ltcodecs.egg-info/entry_points.txt +2 -0
  29. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs.egg-info/scm_file_list.json +46 -39
  30. ltcodecs-4.4.0/src/ltcodecs.egg-info/scm_version.json +8 -0
  31. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/tests/test_codecs.py +250 -1
  32. ltcodecs-4.4.0/tests/test_dict_codec.py +186 -0
  33. ltcodecs-4.4.0/tests/test_inspection.py +133 -0
  34. ltcodecs-4.3.0/src/ltcodecs/variable_len_array_codec.py +0 -113
  35. ltcodecs-4.3.0/src/ltcodecs.egg-info/scm_version.json +0 -8
  36. ltcodecs-4.3.0/tests/test_dict_codec.py +0 -104
  37. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/.gitlab/merge_request_templates/Default.md +0 -0
  38. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/CONTRIBUTING.md +0 -0
  39. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/LICENSE +0 -0
  40. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/README.md +0 -0
  41. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/docs/Makefile +0 -0
  42. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/docs/conf.py +0 -0
  43. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/docs/index.rst +0 -0
  44. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/docs/quickstart.rst +0 -0
  45. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/docs/requirements.txt +0 -0
  46. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/docs/tests.rst +0 -0
  47. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/requirements.txt +0 -0
  48. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/setup.cfg +0 -0
  49. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/bool_codec.py +0 -0
  50. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/ccl_latlon_bcd_codec.py +0 -0
  51. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/ccl_latlon_codec.py +0 -0
  52. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/dict_codec.py +0 -0
  53. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/exceptions.py +0 -0
  54. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/exponentialgolomb_integer_codec.py +0 -0
  55. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/fixed_len_array_codec.py +0 -0
  56. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/fixedint_codec.py +0 -0
  57. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/float_codec.py +0 -0
  58. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/ieee_float_codec.py +0 -0
  59. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/linspace_float_codec.py +0 -0
  60. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/lzma_codec.py +0 -0
  61. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/multiple_field_codec.py +0 -0
  62. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/ros_message_codec.py +0 -0
  63. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/rostime_codec.py +0 -0
  64. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/string_enum_codec.py +0 -0
  65. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs/varint_codec.py +0 -0
  66. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs.egg-info/dependency_links.txt +0 -0
  67. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs.egg-info/requires.txt +0 -0
  68. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/src/ltcodecs.egg-info/top_level.txt +0 -0
  69. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/tests/__init__.py +0 -0
  70. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/tests/dict_codec.yaml +0 -0
  71. {ltcodecs-4.3.0 → ltcodecs-4.4.0}/tests/string_msg_codec.yaml +0 -0
@@ -217,3 +217,5 @@ src/commandSender.py
217
217
  src/database/databases/hostCommands.db
218
218
  src/database/databases/remoteCommands.db
219
219
  src/uploadedFiles/.gitkeep
220
+
221
+ .idea/
@@ -1,6 +1,7 @@
1
1
  include:
2
2
  - project: acomms/ci_templates
3
- ref: v2.3.2
3
+ ref: v3.2.0
4
+ # Note: Don't forget to update CI_TEMPLATES_REF to match this
4
5
  file:
5
6
  - /templates/python-docs.yml
6
7
  - /templates/python-tests.yml
@@ -20,18 +21,20 @@ stages:
20
21
  - deploy
21
22
 
22
23
  variables:
24
+ # Must match the ci_templates include ref when using shared Dockerfiles.
25
+ CI_TEMPLATES_REF: "v3.2.0"
26
+
23
27
  PYTHON_DOCS_PDF_ARTIFACT: "ltcodecs.pdf"
24
28
  PYTHON_DOCS_PDF_SOURCE: "docs/_build/latex/ltcodecs.pdf"
25
29
 
26
30
  ROS_ACOMMS_REF: "devel"
27
31
 
28
- PYTHON_PYTEST_ARGS: "-vv -rA --capture=tee-sys"
29
- PYTHON_PYTEST_COVERAGE_ARGS: "--cov=ltcodecs"
30
- PYTHON_PYTEST_TARGET: "tests/"
31
-
32
32
  native_tests:
33
33
  extends: .armw_native_pytest_template
34
34
  variables:
35
+ PYTHON_PYTEST_ARGS: "-vv -rA --capture=tee-sys"
36
+ PYTHON_PYTEST_COVERAGE_ARGS: "--cov=ltcodecs"
37
+ PYTHON_PYTEST_TARGET: "tests/"
35
38
  NATIVE_TEST_SETUP_COMMANDS: |
36
39
  pip install .
37
40
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ltcodecs
3
- Version: 4.3.0
3
+ Version: 4.4.0
4
4
  Summary: LT Codecs: Lightweight codecs for use over low-throughput links
5
5
  Author: Woods Hole Oceanographic Institution, Acoustic Communications Group
6
6
  License: LGPL-3.0-or-later
@@ -54,6 +54,19 @@ Common scalar aliases include ``string``, ``integer``, ``bool``, ``float``,
54
54
  natural constraint, and ``linspace_float`` when a physical step size or number
55
55
  of values is the natural constraint.
56
56
 
57
+ Constants
58
+ ---------
59
+
60
+ Use ``constant`` for a configured field value that is not sent on the wire.
61
+ The codec emits zero bits, does not require the field to be present in the
62
+ source dictionary, and returns its configured value during decoding.
63
+
64
+ .. code-block:: yaml
65
+
66
+ protocol_version:
67
+ codec: constant
68
+ value: 2
69
+
57
70
  Nested Dictionaries
58
71
  -------------------
59
72
 
@@ -107,7 +120,7 @@ Variable-Length Arrays
107
120
  ----------------------
108
121
 
109
122
  Use ``variable_len_array`` for a list whose encoded length is included in the
110
- bit stream.
123
+ bit stream by default.
111
124
 
112
125
  .. code-block:: yaml
113
126
 
@@ -117,7 +130,8 @@ bit stream.
117
130
  element_type: uint8
118
131
 
119
132
  ``max_length`` is required. Values longer than ``max_length`` are currently
120
- truncated during encoding.
133
+ truncated during encoding. Set ``min_length`` when the list always has at
134
+ least a known number of elements; encoding fails if a shorter list is provided.
121
135
 
122
136
  For element codecs that need parameters, put those parameters under
123
137
  ``element_params``:
@@ -142,6 +156,55 @@ Set ``nullable: true`` when an empty list should encode as a single bit. When
142
156
  nullable: true
143
157
  element_type: uint8
144
158
 
159
+ With ``nullable: true`` and the default ``length_mode: varint``, the initial
160
+ bit indicates whether the list is longer than ``min_length``. If it is not,
161
+ the length prefix is omitted and the decoder reads exactly ``min_length``
162
+ elements.
163
+
164
+ Use ``length_mode: egint`` to encode the array length with exponential-Golomb
165
+ coding instead of the fixed-width ``varint`` length codec:
166
+
167
+ .. code-block:: yaml
168
+
169
+ sparse_samples:
170
+ codec: variable_len_array
171
+ min_length: 2
172
+ max_length: 10
173
+ length_mode: egint
174
+ element_type: uint8
175
+
176
+ Use ``length_mode: fill_outer`` when an outer frame already determines the
177
+ field size. This omits the length prefix and decodes elements sequentially
178
+ until the current bit stream is exhausted. A ``fill_outer`` field must be the
179
+ final encoded field in its containing ``dict`` or ``msg`` frame.
180
+
181
+ .. code-block:: yaml
182
+
183
+ payload:
184
+ codec: variable_len_array
185
+ max_length: 10
186
+ length_mode: fill_outer
187
+ element_type: uint8
188
+
189
+ Bit-Length Byte Arrays
190
+ ----------------------
191
+
192
+ ``variable_len_bit_array`` is a compact codec for a ``uint8`` payload with an
193
+ exact bit length. It replaces a separately encoded size field with a three-bit
194
+ unused-tail count.
195
+
196
+ .. code-block:: yaml
197
+
198
+ payload:
199
+ codec: variable_len_bit_array
200
+ max_length: 65535
201
+ bit_length_field: payload_size_in_bits
202
+
203
+ At encode time, the source message supplies both ``payload`` and
204
+ ``payload_size_in_bits``. The bit-length field must not appear as another
205
+ configured field. Decoding returns the payload and derives
206
+ ``payload_size_in_bits`` from the byte count and trailing unused-bit count.
207
+
145
208
  Fixed-Length Arrays
146
209
  -------------------
147
210
 
@@ -180,6 +243,28 @@ with ``element_params``.
180
243
  z:
181
244
  codec: float32
182
245
 
246
+ An all-constant element message can carry only the array length. Each decoded
247
+ element is still a fresh ROS message instance:
248
+
249
+ .. code-block:: yaml
250
+
251
+ links:
252
+ codec: variable_len_array
253
+ max_length: 7
254
+ element_type: msg
255
+ element_params:
256
+ ros_type: ros_acomms_net_msgs/Link
257
+ fields:
258
+ source:
259
+ codec: constant
260
+ value: 0
261
+ dest:
262
+ codec: constant
263
+ value: 0
264
+ modem:
265
+ codec: constant
266
+ value: ""
267
+
183
268
  Optional Fields
184
269
  ---------------
185
270
 
@@ -45,8 +45,12 @@ Alias Codec Class
45
45
  ``string_enum`` :py:class:`~ltcodecs.string_enum_codec.StringEnumCodec`
46
46
  ``lzma`` :py:class:`~ltcodecs.lzma_codec.LzmaCodec`
47
47
  ``optional`` :py:class:`~ltcodecs.optional_field_codec.OptionalFieldCodec`
48
+ ``constant`` :py:class:`~ltcodecs.constant_codec.ConstantCodec`
48
49
  ======================== ====================================================
49
50
 
51
+ ``variable_len_bit_array``
52
+ :py:class:`~ltcodecs.variable_len_bit_array_codec.VariableLenBitArrayCodec`
53
+
50
54
  Metadata Aliases
51
55
  ----------------
52
56
 
@@ -73,8 +73,17 @@ Container Codecs
73
73
 
74
74
  ``variable_len_array``
75
75
  Encodes a list with a length prefix. ``max_length`` controls the largest
76
- representable list. ``element_type`` and ``element_params`` configure the
77
- element codec.
76
+ representable list, and ``min_length`` can require a lower bound. The
77
+ length prefix uses ``length_codec: varint`` by default; use
78
+ ``length_codec: egint`` when exponential-Golomb coding is a better fit for
79
+ the expected length distribution. ``element_type`` and ``element_params``
80
+ configure the element codec.
81
+
82
+ ``variable_len_bit_array``
83
+ Encodes a variable-length ``uint8`` array and derives an exact bit length
84
+ for a second field. The wire format contains the array length, byte values,
85
+ and a three-bit count of unused final-byte bits. Configure the derived field
86
+ with ``bit_length_field``; it is not a separate configured wire field.
78
87
 
79
88
  ``fixed_len_array``
80
89
  Encodes a fixed number of elements. The encoded bitstream does not include
@@ -84,6 +93,11 @@ Container Codecs
84
93
  Encodes a boolean presence field. When true, the configured
85
94
  ``target_fields`` are encoded immediately after the presence bit.
86
95
 
96
+ ``constant``
97
+ Returns its configured ``value`` without consuming payload bits. This is
98
+ useful for decoded fields that are known by configuration rather than sent
99
+ on the wire. Constant fields do not need a source value during encoding.
100
+
87
101
  Special-Purpose Codecs
88
102
  ----------------------
89
103
 
@@ -37,6 +37,16 @@ Common Field Keys
37
37
  Maximum number of characters, bytes, or array elements for variable-length
38
38
  codecs.
39
39
 
40
+ ``min_length``
41
+ Minimum number of elements for ``variable_len_array``. Encoding fails when
42
+ the provided list is shorter than this value.
43
+
44
+ ``length_mode``
45
+ Length handling mode for ``string``, ``bytes``, and
46
+ ``variable_len_array``. Supported values are ``varint``, ``egint``, and
47
+ ``fill_outer``. ``fill_outer`` omits the length prefix and relies on the
48
+ containing frame to define the field size.
49
+
40
50
  ``length``
41
51
  Number of elements for ``fixed_len_array``.
42
52
 
@@ -46,6 +56,14 @@ Common Field Keys
46
56
  ``element_params``
47
57
  Nested configuration passed to the element codec for an array.
48
58
 
59
+ ``bit_length_field``
60
+ Name of the derived bit-length field emitted by ``variable_len_bit_array``
61
+ during decoding. The named field is supplied when encoding but must not be
62
+ configured separately in the same mapping.
63
+
64
+ ``value``
65
+ YAML-native value returned by the zero-bit ``constant`` codec.
66
+
49
67
  ``nullable``
50
68
  When true on ``variable_len_array``, an empty list is encoded as a single
51
69
  absent/present bit.
@@ -91,15 +109,52 @@ Nested ROS messages use ``ros_type`` and may optionally provide ``fields``:
91
109
  Arrays
92
110
  ------
93
111
 
94
- Variable-length arrays include a length prefix:
112
+ Variable-length arrays include a length prefix by default:
113
+
114
+ .. code-block:: yaml
115
+
116
+ samples:
117
+ codec: variable_len_array
118
+ max_length: 8
119
+ element_type: uint16
120
+
121
+ Use ``min_length`` when every encoded array has at least a known number of
122
+ elements. With ``nullable: true`` and ``length_mode: varint``, a false flag
123
+ means the array has exactly ``min_length`` elements, so the length prefix is
124
+ omitted in that case.
125
+
126
+ .. code-block:: yaml
127
+
128
+ samples:
129
+ codec: variable_len_array
130
+ min_length: 2
131
+ max_length: 8
132
+ nullable: true
133
+ element_type: uint16
134
+
135
+ Use ``length_mode: egint`` to encode lengths with exponential-Golomb coding:
95
136
 
96
137
  .. code-block:: yaml
97
138
 
98
139
  samples:
99
140
  codec: variable_len_array
141
+ min_length: 2
100
142
  max_length: 8
143
+ length_mode: egint
101
144
  element_type: uint16
102
145
 
146
+ Use ``length_mode: fill_outer`` for strings, bytes, or variable-length arrays
147
+ whose size is defined by an outer frame rather than an internal prefix. A
148
+ ``fill_outer`` field must be the final encoded field in its containing
149
+ ``dict`` or ``msg`` frame.
150
+
151
+ .. code-block:: yaml
152
+
153
+ payload:
154
+ codec: bytes
155
+ max_length: 32
156
+ length_mode: fill_outer
157
+
103
158
  Fixed-length arrays omit the length prefix:
104
159
 
105
160
  .. code-block:: yaml
@@ -138,6 +193,45 @@ Arrays of ROS messages use ``element_type: msg``:
138
193
  z:
139
194
  codec: float32
140
195
 
196
+ Bit-Length Byte Arrays
197
+ ----------------------
198
+
199
+ Use ``variable_len_bit_array`` for a ``uint8`` payload whose last byte can
200
+ contain between zero and seven unused bits. The configuration key is the byte
201
+ array field. ``bit_length_field`` names the related value that is derived on
202
+ decode, so it is omitted as a separate field configuration.
203
+
204
+ .. code-block:: yaml
205
+
206
+ payload:
207
+ codec: variable_len_bit_array
208
+ max_length: 65535
209
+ bit_length_field: payload_size_in_bits
210
+
211
+ For a two-byte payload with ``payload_size_in_bits: 13``, the codec writes the
212
+ normal length prefix and two bytes followed by a three-bit value of ``3``. On
213
+ decode it returns the byte array and derives ``payload_size_in_bits`` as
214
+ ``13``. The supplied bit length must describe the final payload byte: it must
215
+ be between ``8 * len(payload) - 7`` and ``8 * len(payload)``, or zero for an
216
+ empty payload.
217
+
218
+ Constants
219
+ ---------
220
+
221
+ Use ``constant`` for a configured value that consumes no bits. The value is
222
+ returned during decoding and replaces any source value during encoding.
223
+
224
+ .. code-block:: yaml
225
+
226
+ source:
227
+ codec: constant
228
+ value: 0
229
+
230
+ This is especially useful in explicit nested ROS message configurations. A
231
+ ``msg`` codec whose configured children are all constants can be encoded
232
+ without an element object; a variable-length array of those messages can use
233
+ ``None`` placeholders solely to communicate its length.
234
+
141
235
  Top-Level Options
142
236
  -----------------
143
237
 
@@ -20,6 +20,14 @@ ltcodecs.bytes\_codec module
20
20
  :undoc-members:
21
21
  :show-inheritance:
22
22
 
23
+ ltcodecs.constant\_codec module
24
+ -------------------------------
25
+
26
+ .. automodule:: ltcodecs.constant_codec
27
+ :members:
28
+ :undoc-members:
29
+ :show-inheritance:
30
+
23
31
  ltcodecs.ccl\_latlon\_bcd\_codec module
24
32
  ---------------------------------------
25
33
 
@@ -189,6 +197,14 @@ ltcodecs.variable\_len\_array\_codec module
189
197
  :undoc-members:
190
198
  :show-inheritance:
191
199
 
200
+ ltcodecs.variable\_len\_bit\_array\_codec module
201
+ ---------------------------------------------------
202
+
203
+ .. automodule:: ltcodecs.variable_len_bit_array_codec
204
+ :members:
205
+ :undoc-members:
206
+ :show-inheritance:
207
+
192
208
  ltcodecs.varint\_codec module
193
209
  -----------------------------
194
210
 
@@ -114,6 +114,18 @@ is treated like this explicit configuration:
114
114
  The default maximum length for inferred variable-length arrays is currently
115
115
  ``10``. Use an explicit config when that limit should be different.
116
116
 
117
+ For a ROS ``uint8`` payload whose exact bit length is also needed, use the
118
+ explicit ``variable_len_bit_array`` codec. The payload field remains a normal
119
+ ROS array; the named bit-length field is derived during decoding rather than
120
+ encoded as a separate field.
121
+
122
+ .. code-block:: yaml
123
+
124
+ payload:
125
+ codec: variable_len_bit_array
126
+ max_length: 65535
127
+ bit_length_field: payload_size_in_bits
128
+
117
129
  Nested Message Fields
118
130
  ---------------------
119
131
 
@@ -137,6 +149,32 @@ encoded or when individual nested fields need custom codecs.
137
149
  Without a ``fields`` mapping, ``RosMsgFieldCodec`` infers the nested message's
138
150
  fields from the ROS message definition.
139
151
 
152
+ Constant Nested Fields
153
+ ----------------------
154
+
155
+ Use ``constant`` to reconstruct nested fields without transmitting them. A
156
+ constant field consumes zero bits and returns its configured YAML value on
157
+ decode.
158
+
159
+ .. code-block:: yaml
160
+
161
+ link:
162
+ codec: msg
163
+ ros_type: ros_acomms_net_msgs/Link
164
+ fields:
165
+ source:
166
+ codec: constant
167
+ value: 0
168
+ dest:
169
+ codec: constant
170
+ value: 0
171
+
172
+ When every configured child of a nested ``msg`` is constant, the message codec
173
+ does not require a source message while encoding. This permits a
174
+ ``variable_len_array`` of such messages to use ``None`` values as input solely
175
+ to represent the desired array length; decoding constructs a fresh ROS message
176
+ for every element.
177
+
140
178
  Metadata
141
179
  --------
142
180
 
@@ -26,7 +26,7 @@ homepage = "https://git.whoi.edu/acomms/ltcodec/"
26
26
  documentation = "https://acomms.pages.whoi.edu/ltcodec/"
27
27
 
28
28
  [project.scripts]
29
- # No scripts for now; we may add scripts in /bin in a future release
29
+ inspect_codec = "ltcodecs.inspect_cli:main"
30
30
 
31
31
  [project.optional-dependencies]
32
32
  # No optional dependencies
@@ -36,4 +36,4 @@ requires = ["setuptools>=64", "wheel", "setuptools_scm>=8"]
36
36
  build-backend = "setuptools.build_meta"
37
37
 
38
38
  [tool.setuptools_scm]
39
- write_to = "_version.py"
39
+ write_to = "_version.py"
@@ -10,19 +10,23 @@ from .bool_codec import BoolCodec
10
10
  from .string_codecs import AsciiStringCodec
11
11
  from .string_enum_codec import StringEnumCodec
12
12
  from .bytes_codec import BytesCodec
13
+ from .metadata_field_codec import MetadataFieldCodec
13
14
  from .variable_len_array_codec import VariableLenArrayCodec
15
+ from .variable_len_bit_array_codec import VariableLenBitArrayCodec
14
16
  from .fixed_len_array_codec import FixedLenArrayCodec
15
17
  from .dict_field_codec import DictFieldCodec
16
18
  from .ros_msg_field_codec import RosMsgFieldCodec
17
19
  from .ccl_latlon_codec import CclLatLonCodec
18
20
  from .ccl_latlon_bcd_codec import CclLatLonBcdCodec
19
21
  from .padding_codec import PaddingCodec
22
+ from .constant_codec import ConstantCodec
20
23
  from .rostime_codec import RosTimeCodec
21
24
  from .dict_codec import DictCodec
22
25
  from .ros_message_codec import RosMessageCodec
23
26
  from .lzma_codec import LzmaCodec
24
27
  from .optional_field_codec import OptionalFieldCodec
25
28
  from .exceptions import EncodingFailed
29
+ from .inspection import CodecInspector, FieldReport, InspectionReport, ValidationIssue
26
30
 
27
31
  field_codec_classes = {
28
32
  "integer": VarintCodec,
@@ -52,11 +56,13 @@ field_codec_classes = {
52
56
  "msg": RosMsgFieldCodec,
53
57
  "ros_msg": RosMsgFieldCodec,
54
58
  "variable_len_array": VariableLenArrayCodec,
59
+ "variable_len_bit_array": VariableLenBitArrayCodec,
55
60
  "fixed_len_array": FixedLenArrayCodec,
56
61
  "ccl_latlon": CclLatLonCodec,
57
62
  "ccl_latlon_bcd": CclLatLonBcdCodec,
58
63
  "pad": PaddingCodec,
59
64
  "padding": PaddingCodec,
65
+ "constant": ConstantCodec,
60
66
  "time": RosTimeCodec,
61
67
  "rostime": RosTimeCodec,
62
68
  "string_enum": StringEnumCodec,
@@ -0,0 +1,61 @@
1
+ """
2
+ Shared helpers for codecs that encode variable-length values.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from bitstring import BitArray, ConstBitStream
8
+
9
+ from .exponentialgolomb_integer_codec import ExponentialGolombIntegerCodec
10
+ from .varint_codec import VarintCodec
11
+
12
+
13
+ VALID_LENGTH_MODES = ("varint", "egint", "fill_outer")
14
+ PREFIXED_LENGTH_MODES = ("varint", "egint")
15
+ FILL_OUTER_LENGTH_MODE = "fill_outer"
16
+
17
+
18
+ def validate_length_mode(length_mode: str, codec_name: str) -> str:
19
+ if length_mode not in VALID_LENGTH_MODES:
20
+ raise ValueError(
21
+ f"{codec_name} has invalid length_mode parameter: {length_mode}, "
22
+ f"should be one of {', '.join(VALID_LENGTH_MODES)}."
23
+ )
24
+ return length_mode
25
+
26
+
27
+ def make_length_codec(length_mode: str, min_value: int, max_value: int):
28
+ if length_mode == "varint":
29
+ return VarintCodec(min_value=min_value, max_value=max_value)
30
+ if length_mode == "egint":
31
+ return ExponentialGolombIntegerCodec(min_value=min_value, max_value=max_value)
32
+ return None
33
+
34
+
35
+ def encode_length(length_mode: str, length_codec, length: int) -> BitArray:
36
+ if length_mode == FILL_OUTER_LENGTH_MODE:
37
+ return BitArray()
38
+ length_bits, _ = length_codec.encode(length)
39
+ return BitArray(length_bits)
40
+
41
+
42
+ def decode_length(length_mode: str, length_codec, bits_to_decode: ConstBitStream) -> int:
43
+ if length_mode == FILL_OUTER_LENGTH_MODE:
44
+ raise ValueError("fill_outer length mode does not encode a length prefix")
45
+ return length_codec.decode(bits_to_decode)
46
+
47
+
48
+ def remaining_bits(bits_to_decode: ConstBitStream) -> int:
49
+ return len(bits_to_decode) - bits_to_decode.pos
50
+
51
+
52
+ def prefix_min_length_bits(length_mode: str, length_codec) -> int:
53
+ if length_mode == FILL_OUTER_LENGTH_MODE:
54
+ return 0
55
+ return length_codec.min_length_bits
56
+
57
+
58
+ def prefix_max_length_bits(length_mode: str, length_codec) -> int:
59
+ if length_mode == FILL_OUTER_LENGTH_MODE:
60
+ return 0
61
+ return length_codec.max_length_bits
@@ -8,7 +8,16 @@ This module contains the BytesCodec class, which is used to encode and decode by
8
8
  from __future__ import annotations
9
9
  from bitstring import BitArray, ConstBitStream, Bits
10
10
  from .field_codec import FieldCodec
11
- from .varint_codec import VarintCodec
11
+ from ._length_modes import (
12
+ FILL_OUTER_LENGTH_MODE,
13
+ decode_length,
14
+ encode_length,
15
+ make_length_codec,
16
+ prefix_max_length_bits,
17
+ prefix_min_length_bits,
18
+ remaining_bits,
19
+ validate_length_mode,
20
+ )
12
21
  from .exceptions import EncodingFailed
13
22
 
14
23
 
@@ -17,9 +26,10 @@ class BytesCodec(FieldCodec):
17
26
  codec for bytes
18
27
  """
19
28
 
20
- def __init__(self, max_length: int, fail_on_overflow=False, **kwargs) -> None:
29
+ def __init__(self, max_length: int, fail_on_overflow=False, length_mode: str = "varint", **kwargs) -> None:
21
30
  self.max_length = max_length
22
- self.length_codec = VarintCodec(min_value=0, max_value=self.max_length)
31
+ self.length_mode = validate_length_mode(length_mode, "BytesCodec")
32
+ self.length_codec = make_length_codec(self.length_mode, min_value=0, max_value=self.max_length)
23
33
  self.fail_on_overflow = fail_on_overflow
24
34
 
25
35
  def encode(self, value: bytes) -> tuple[Bits, bytes]:
@@ -33,9 +43,8 @@ class BytesCodec(FieldCodec):
33
43
  f"BytesCodec: value with length {len(value)} is too long to encode (codec max_length={self.max_length}"
34
44
  )
35
45
  value = value[0 : self.max_length]
36
- length_bits, _ = self.length_codec.encode(len(value))
37
46
  value_bits = BitArray(bytes=value)
38
- value_bits.prepend(length_bits)
47
+ value_bits.prepend(encode_length(self.length_mode, self.length_codec, len(value)))
39
48
  return value_bits, value
40
49
 
41
50
  def decode(self, bits_to_decode: ConstBitStream) -> bytes:
@@ -44,14 +53,24 @@ class BytesCodec(FieldCodec):
44
53
 
45
54
  :param bits_to_decode: the bits to decode
46
55
  """
47
- num_bytes = self.length_codec.decode(bits_to_decode)
56
+ if self.length_mode == FILL_OUTER_LENGTH_MODE:
57
+ num_bits = remaining_bits(bits_to_decode)
58
+ if num_bits % 8:
59
+ raise ValueError(
60
+ "BytesCodec length_mode='fill_outer' received bits that do not align to bytes."
61
+ )
62
+ num_bytes = num_bits // 8
63
+ if num_bytes > self.max_length:
64
+ raise ValueError(f"BytesCodec decoded {num_bytes} bytes, exceeding max_length={self.max_length}.")
65
+ else:
66
+ num_bytes = decode_length(self.length_mode, self.length_codec, bits_to_decode)
48
67
  value = bits_to_decode.read("bytes:" + str(num_bytes))
49
68
  return value
50
69
 
51
70
  @property
52
71
  def min_length_bits(self) -> int:
53
- return self.length_codec.max_length_bits
72
+ return prefix_min_length_bits(self.length_mode, self.length_codec)
54
73
 
55
74
  @property
56
75
  def max_length_bits(self) -> int:
57
- return self.length_codec.max_length_bits + (8 * self.max_length)
76
+ return prefix_max_length_bits(self.length_mode, self.length_codec) + (8 * self.max_length)
@@ -0,0 +1,41 @@
1
+ """Zero-bit codec for configured constant values."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from copy import deepcopy
6
+ from typing import Any
7
+
8
+ from bitstring import BitArray, ConstBitStream
9
+
10
+ from .field_codec import FieldCodec
11
+
12
+
13
+ class ConstantCodec(FieldCodec):
14
+ """Return a configured value without encoding it on the wire.
15
+
16
+ The source message need not contain a field using this codec. Each encode
17
+ and decode operation returns a deep copy so mutable configured values are
18
+ never shared between decoded messages.
19
+
20
+ Args:
21
+ value: YAML-native value returned during decoding.
22
+ """
23
+
24
+ requires_encode_value = False
25
+
26
+ def __init__(self, value: Any, **kwargs: Any) -> None:
27
+ self.value = value
28
+
29
+ def encode(self, value: Any = None) -> tuple[BitArray, Any]:
30
+ return BitArray(), deepcopy(self.value)
31
+
32
+ def decode(self, bits_to_decode: ConstBitStream) -> Any:
33
+ return deepcopy(self.value)
34
+
35
+ @property
36
+ def min_length_bits(self) -> int:
37
+ return 0
38
+
39
+ @property
40
+ def max_length_bits(self) -> int:
41
+ return 0