diff --git a/openc3/ext/openc3/ext/polynomial_conversion/polynomial_conversion.c b/openc3/ext/openc3/ext/polynomial_conversion/polynomial_conversion.c index 1a821b60b3..b53e7cbe44 100644 --- a/openc3/ext/openc3/ext/polynomial_conversion/polynomial_conversion.c +++ b/openc3/ext/openc3/ext/polynomial_conversion/polynomial_conversion.c @@ -15,7 +15,7 @@ /* # Modified by OpenC3, Inc. -# All changes Copyright 2022, OpenC3, Inc. +# All changes Copyright 2026, OpenC3, Inc. # All Rights Reserved # # This file may also be used under the terms of a commercial license @@ -47,9 +47,18 @@ static VALUE polynomial_conversion_call(VALUE self, VALUE value, VALUE myself, V int index = 0; double converted = 0.0; double raised_to_power = 1.0; - volatile VALUE coeffs = rb_ivar_get(self, id_ivar_coeffs); - long coeffs_length = RARRAY_LEN(coeffs); - double double_value = RFLOAT_VALUE(rb_funcall(value, id_method_to_f, 0)); + volatile VALUE coeffs; + long coeffs_length; + double double_value; + + /* Return nil if value is nil (item outside buffer bounds) */ + if (NIL_P(value)) { + return Qnil; + } + + coeffs = rb_ivar_get(self, id_ivar_coeffs); + coeffs_length = RARRAY_LEN(coeffs); + double_value = RFLOAT_VALUE(rb_funcall(value, id_method_to_f, 0)); /* Handle C0 */ double coeff = RFLOAT_VALUE(rb_ary_entry(coeffs, 0)); diff --git a/openc3/ext/openc3/ext/structure/structure.c b/openc3/ext/openc3/ext/structure/structure.c index 048a396c3a..f985a324e3 100644 --- a/openc3/ext/openc3/ext/structure/structure.c +++ b/openc3/ext/openc3/ext/structure/structure.c @@ -15,7 +15,7 @@ /* # Modified by OpenC3, Inc. -# All changes Copyright 2024, OpenC3, Inc. +# All changes Copyright 2026, OpenC3, Inc. # All Rights Reserved # # This file may also be used under the terms of a commercial license @@ -520,7 +520,8 @@ static VALUE binary_accessor_read(VALUE self, VALUE param_bit_offset, VALUE para if (!check_bounds_and_buffer_size(bit_offset, bit_size, (int)buffer_length, param_endianness, param_data_type, &lower_bound, &upper_bound)) { - rb_funcall(self, id_method_raise_buffer_error, 5, symbol_read, param_buffer, param_data_type, param_bit_offset, param_bit_size); + /* Return nil for out-of-bounds reads (supports undersized packets with ALLOW_SHORT) */ + return Qnil; } if ((param_data_type == symbol_STRING) || (param_data_type == symbol_BLOCK)) diff --git a/openc3/lib/openc3/accessors/accessor.rb b/openc3/lib/openc3/accessors/accessor.rb index b15ecb1723..87337b23e7 100644 --- a/openc3/lib/openc3/accessors/accessor.rb +++ b/openc3/lib/openc3/accessors/accessor.rb @@ -85,7 +85,7 @@ def enforce_length # This sets the short_buffer_allowed flag in the Packet class # which allows packets that have a buffer shorter than the defined size. - # Note that the buffer is still resized to the defined length + # Items outside the buffer bounds will return nil when read. def enforce_short_buffer_allowed return false end diff --git a/openc3/lib/openc3/accessors/binary_accessor.rb b/openc3/lib/openc3/accessors/binary_accessor.rb index 8c301303fc..993a1c44ab 100644 --- a/openc3/lib/openc3/accessors/binary_accessor.rb +++ b/openc3/lib/openc3/accessors/binary_accessor.rb @@ -14,7 +14,7 @@ # GNU Affero General Public License for more details. # Modified by OpenC3, Inc. -# All changes Copyright 2024, OpenC3, Inc. +# All changes Copyright 2026, OpenC3, Inc. # All Rights Reserved # # This file may also be used under the terms of a commercial license @@ -339,7 +339,8 @@ def self.read(bit_offset, bit_size, data_type, buffer, endianness) end result, lower_bound, upper_bound = check_bounds_and_buffer_size(bit_offset, bit_size, buffer.length, endianness, data_type) - raise_buffer_error(:read, buffer, data_type, given_bit_offset, given_bit_size) unless result + # Return nil for out-of-bounds reads (supports undersized packets with ALLOW_SHORT) + return nil unless result if (data_type == :STRING) || (data_type == :BLOCK) ####################################### @@ -936,6 +937,9 @@ def self.read_array(bit_offset, bit_size, data_type, array_size, buffer, endiann lower_bound = bit_offset / 8 upper_bound = (bit_offset + array_size - 1) / 8 + # Return nil for out-of-bounds reads (supports undersized packets with ALLOW_SHORT) + return nil if upper_bound >= buffer.length + # Check for byte alignment byte_aligned = ((bit_offset % 8) == 0) @@ -1420,7 +1424,7 @@ def enforce_length # This sets the short_buffer_allowed flag in the Packet class # which allows packets that have a buffer shorter than the defined size. - # Note that the buffer is still resized to the defined length + # Items outside the buffer bounds will return nil when read. def enforce_short_buffer_allowed return false end diff --git a/openc3/lib/openc3/accessors/form_accessor.rb b/openc3/lib/openc3/accessors/form_accessor.rb index 48c0f85b08..d6b2f7f553 100644 --- a/openc3/lib/openc3/accessors/form_accessor.rb +++ b/openc3/lib/openc3/accessors/form_accessor.rb @@ -75,7 +75,7 @@ def enforce_length # This sets the short_buffer_allowed flag in the Packet class # which allows packets that have a buffer shorter than the defined size. - # Note that the buffer is still resized to the defined length + # Items outside the buffer bounds will return nil when read. def enforce_short_buffer_allowed return true end diff --git a/openc3/lib/openc3/accessors/http_accessor.rb b/openc3/lib/openc3/accessors/http_accessor.rb index 418a32f8f5..00fce951a4 100644 --- a/openc3/lib/openc3/accessors/http_accessor.rb +++ b/openc3/lib/openc3/accessors/http_accessor.rb @@ -164,7 +164,7 @@ def enforce_length # This sets the short_buffer_allowed flag in the Packet class # which allows packets that have a buffer shorter than the defined size. - # Note that the buffer is still resized to the defined length + # Items outside the buffer bounds will return nil when read. def enforce_short_buffer_allowed return @body_accessor.enforce_short_buffer_allowed end diff --git a/openc3/lib/openc3/accessors/json_accessor.rb b/openc3/lib/openc3/accessors/json_accessor.rb index 75dab54ecf..af9c7826b9 100644 --- a/openc3/lib/openc3/accessors/json_accessor.rb +++ b/openc3/lib/openc3/accessors/json_accessor.rb @@ -158,7 +158,7 @@ def enforce_length # This sets the short_buffer_allowed flag in the Packet class # which allows packets that have a buffer shorter than the defined size. - # Note that the buffer is still resized to the defined length + # Items outside the buffer bounds will return nil when read. def enforce_short_buffer_allowed return true end diff --git a/openc3/lib/openc3/accessors/template_accessor.rb b/openc3/lib/openc3/accessors/template_accessor.rb index 69bd09ffe3..71e64ff45f 100644 --- a/openc3/lib/openc3/accessors/template_accessor.rb +++ b/openc3/lib/openc3/accessors/template_accessor.rb @@ -134,7 +134,7 @@ def enforce_length # This sets the short_buffer_allowed flag in the Packet class # which allows packets that have a buffer shorter than the defined size. - # Note that the buffer is still resized to the defined length + # Items outside the buffer bounds will return nil when read. def enforce_short_buffer_allowed return true end diff --git a/openc3/lib/openc3/accessors/xml_accessor.rb b/openc3/lib/openc3/accessors/xml_accessor.rb index 4afd492848..11e46e1f5e 100644 --- a/openc3/lib/openc3/accessors/xml_accessor.rb +++ b/openc3/lib/openc3/accessors/xml_accessor.rb @@ -95,7 +95,7 @@ def enforce_length # This sets the short_buffer_allowed flag in the Packet class # which allows packets that have a buffer shorter than the defined size. - # Note that the buffer is still resized to the defined length + # Items outside the buffer bounds will return nil when read. def enforce_short_buffer_allowed return true end diff --git a/openc3/lib/openc3/conversions/polynomial_conversion.rb b/openc3/lib/openc3/conversions/polynomial_conversion.rb index e91b0a31ed..dd49ed4785 100644 --- a/openc3/lib/openc3/conversions/polynomial_conversion.rb +++ b/openc3/lib/openc3/conversions/polynomial_conversion.rb @@ -14,7 +14,7 @@ # GNU Affero General Public License for more details. # Modified by OpenC3, Inc. -# All changes Copyright 2024, OpenC3, Inc. +# All changes Copyright 2026, OpenC3, Inc. # All Rights Reserved # # This file may also be used under the terms of a commercial license @@ -45,6 +45,9 @@ def initialize(*coeffs) # @param (see Conversion#call) # @return [Float] The value with the polynomial applied def call(value, myself, buffer) + # Return nil if value is nil (item outside buffer bounds) + return nil if value.nil? + value = value.to_f # Handle C0 diff --git a/openc3/lib/openc3/conversions/segmented_polynomial_conversion.rb b/openc3/lib/openc3/conversions/segmented_polynomial_conversion.rb index 95d16057e1..89b3e5c4cb 100644 --- a/openc3/lib/openc3/conversions/segmented_polynomial_conversion.rb +++ b/openc3/lib/openc3/conversions/segmented_polynomial_conversion.rb @@ -14,7 +14,7 @@ # GNU Affero General Public License for more details. # Modified by OpenC3, Inc. -# All changes Copyright 2024, OpenC3, Inc. +# All changes Copyright 2026, OpenC3, Inc. # All Rights Reserved # # This file may also be used under the terms of a commercial license @@ -116,6 +116,9 @@ def add_segment(lower_bound, *coeffs) # @param (see Conversion#call) # @return [Float] The value with the polynomial applied def call(value, packet, buffer) + # Return nil if value is nil (item outside buffer bounds) + return nil if value.nil? + # Try to find correct segment @segments.each do |segment| return segment.calculate(value) if value >= segment.lower_bound diff --git a/openc3/lib/openc3/packets/packet.rb b/openc3/lib/openc3/packets/packet.rb index cc9ced9c99..3b1ae89049 100644 --- a/openc3/lib/openc3/packets/packet.rb +++ b/openc3/lib/openc3/packets/packet.rb @@ -1049,6 +1049,9 @@ def check_limits(limits_set = :DEFAULT, ignore_persistence = false) if item.limits.enabled value = read_item(item) + # Skip limits checking if value is nil (item outside buffer bounds) + next if value.nil? + # Handle state monitoring and value monitoring differently if item.states handle_limits_states(item, value) @@ -1526,6 +1529,9 @@ def handle_limits_values(item, value, limits_set, ignore_persistence) end def apply_format_string_and_units(item, value, value_type) + # Return nil as-is - can't format a value that doesn't exist + return nil if value.nil? + if value_type == :FORMATTED or value_type == :WITH_UNITS if item.format_string && value value = sprintf(item.format_string, value) diff --git a/openc3/lib/openc3/packets/packet_config.rb b/openc3/lib/openc3/packets/packet_config.rb index 4cdd70f396..cc808c5215 100644 --- a/openc3/lib/openc3/packets/packet_config.rb +++ b/openc3/lib/openc3/packets/packet_config.rb @@ -493,8 +493,8 @@ def process_current_packet(parser, keyword, params) 'APPEND_ARRAY_ITEM', 'APPEND_ARRAY_PARAMETER', 'STRUCTURE', 'APPEND_STRUCTURE' start_item(parser) - # Allow this packet to be received with less data than the defined length - # without generating a warning. + # Allow this packet to be received with less data than the defined length. + # Items that are beyond the buffer will return nil when read. when 'ALLOW_SHORT' @current_packet.short_buffer_allowed = true diff --git a/openc3/lib/openc3/packets/structure.rb b/openc3/lib/openc3/packets/structure.rb index a577be5ade..e843f3d2fc 100644 --- a/openc3/lib/openc3/packets/structure.rb +++ b/openc3/lib/openc3/packets/structure.rb @@ -14,7 +14,7 @@ # GNU Affero General Public License for more details. # Modified by OpenC3, Inc. -# All changes Copyright 2024, OpenC3, Inc. +# All changes Copyright 2026, OpenC3, Inc. # All Rights Reserved # # This file may also be used under the terms of a commercial license @@ -680,8 +680,12 @@ def internal_buffer_equals(buffer) if @accessor.enforce_length if @buffer.length != @defined_length if @buffer.length < @defined_length - resize_buffer() - raise "Buffer length less than defined length" unless @short_buffer_allowed + # Only resize if short_buffer_allowed is false + # When short_buffer_allowed is true, keep the buffer short so reads + # of items beyond the buffer return nil + unless @short_buffer_allowed + raise "Buffer length less than defined length" + end elsif @fixed_size and @defined_length != 0 raise "Buffer length greater than defined length" end diff --git a/openc3/python/openc3/accessors/binary_accessor.py b/openc3/python/openc3/accessors/binary_accessor.py index 9f42959476..dcc7ae7e01 100644 --- a/openc3/python/openc3/accessors/binary_accessor.py +++ b/openc3/python/openc3/accessors/binary_accessor.py @@ -1,4 +1,4 @@ -# Copyright 2025 OpenC3, Inc. +# Copyright 2026 OpenC3, Inc. # All Rights Reserved. # # This program is free software; you can modify and/or redistribute it @@ -105,7 +105,9 @@ def raise_buffer_error(cls, read_write, buffer, data_type, given_bit_offset, giv def handle_read_variable_bit_size(self, item, _buffer): length_value = self.packet.read(item.variable_bit_size["length_item_name"], "CONVERTED") if item.array_size is not None: - item.array_size = (length_value * item.variable_bit_size["length_bits_per_count"]) + item.variable_bit_size["length_value_bit_offset"] + item.array_size = (length_value * item.variable_bit_size["length_bits_per_count"]) + item.variable_bit_size[ + "length_value_bit_offset" + ] else: if item.data_type == "INT" or item.data_type == "UINT": # QUIC encoding is currently assumed for individual variable sized integers @@ -133,7 +135,7 @@ def read_item(self, item, buffer): # Structure is used to read items with parent, not accessor structure_buffer = self.read_item(item.parent_item, buffer) structure = item.parent_item.structure - return structure.read(item.key, 'RAW', structure_buffer) + return structure.read(item.key, "RAW", structure_buffer) else: if item.variable_bit_size: self.handle_read_variable_bit_size(item, buffer) @@ -291,7 +293,7 @@ def write_item(self, item, value, buffer): # Structure is used to write items with parent, not accessor structure_buffer = self.read_item(item.parent_item, buffer) structure = item.parent_item.structure - structure.write(item.key, value, 'RAW', structure_buffer) + structure.write(item.key, value, "RAW", structure_buffer) if item.parent_item.variable_bit_size: self.handle_write_variable_bit_size(item.parent_item, structure_buffer, buffer) BinaryAccessor.class_write_item(item.parent_item, structure_buffer, buffer) @@ -355,8 +357,9 @@ def read(cls, bit_offset, bit_size, data_type, buffer, endianness): result, lower_bound, upper_bound = cls.check_bounds_and_buffer_size( bit_offset, bit_size, len(buffer), endianness, data_type ) + # Return None for out-of-bounds reads (supports undersized packets with ALLOW_SHORT) if not result: - cls.raise_buffer_error("read", buffer, data_type, given_bit_offset, given_bit_size) + return None if data_type in ["STRING", "BLOCK"]: ####################################### @@ -871,6 +874,10 @@ def read_array(cls, bit_offset, bit_size, data_type, array_size, buffer, endiann lower_bound = math.floor(bit_offset / 8) upper_bound = math.floor((bit_offset + array_size - 1) / 8) + # Return None for out-of-bounds reads (supports undersized packets with ALLOW_SHORT) + if upper_bound >= len(buffer): + return None + # Check for byte alignment byte_aligned = (bit_offset % 8) == 0 @@ -936,9 +943,7 @@ def read_array(cls, bit_offset, bit_size, data_type, array_size, buffer, endiann ) ) else: - raise ValueError( - f"bit_size is {given_bit_size} but must be 32 or 64 for data_type {data_type}" - ) + raise ValueError(f"bit_size is {given_bit_size} but must be 32 or 64 for data_type {data_type}") else: raise ValueError(f"bit_offset {given_bit_offset} is not byte aligned for data_type {data_type}") @@ -1149,9 +1154,7 @@ def write_array( *values, ) else: - raise ValueError( - f"bit_size is {given_bit_size} but must be 32 or 64 for data_type {data_type}" - ) + raise ValueError(f"bit_size is {given_bit_size} but must be 32 or 64 for data_type {data_type}") else: raise ValueError(f"bit_offset {given_bit_offset} is not byte aligned for data_type {data_type}") diff --git a/openc3/python/openc3/accessors/form_accessor.py b/openc3/python/openc3/accessors/form_accessor.py index 19fea78cce..5d3a35b16d 100644 --- a/openc3/python/openc3/accessors/form_accessor.py +++ b/openc3/python/openc3/accessors/form_accessor.py @@ -63,7 +63,7 @@ def enforce_length(self): # This sets the short_buffer_allowed flag in the Packet class # which allows packets that have a buffer shorter than the defined size. - # Note that the buffer is still resized to the defined length + # Items outside the buffer bounds will return None when read. def enforce_short_buffer_allowed(self): return True diff --git a/openc3/python/openc3/accessors/http_accessor.py b/openc3/python/openc3/accessors/http_accessor.py index 7ecc228d61..52e5329ef4 100644 --- a/openc3/python/openc3/accessors/http_accessor.py +++ b/openc3/python/openc3/accessors/http_accessor.py @@ -163,7 +163,7 @@ def enforce_length(self): # This sets the short_buffer_allowed flag in the Packet class # which allows packets that have a buffer shorter than the defined size. - # Note that the buffer is still resized to the defined length + # Items outside the buffer bounds will return None when read. def enforce_short_buffer_allowed(self): return self.body_accessor.enforce_short_buffer_allowed() diff --git a/openc3/python/openc3/accessors/template_accessor.py b/openc3/python/openc3/accessors/template_accessor.py index c2478dbd43..d2139cffa2 100644 --- a/openc3/python/openc3/accessors/template_accessor.py +++ b/openc3/python/openc3/accessors/template_accessor.py @@ -157,7 +157,7 @@ def enforce_length(self): # This sets the short_buffer_allowed flag in the Packet class # which allows packets that have a buffer shorter than the defined size. - # Note that the buffer is still resized to the defined length + # Items outside the buffer bounds will return None when read. def enforce_short_buffer_allowed(self): return True diff --git a/openc3/python/openc3/conversions/polynomial_conversion.py b/openc3/python/openc3/conversions/polynomial_conversion.py index 59297881cc..bebedc24b1 100644 --- a/openc3/python/openc3/conversions/polynomial_conversion.py +++ b/openc3/python/openc3/conversions/polynomial_conversion.py @@ -1,4 +1,4 @@ -# Copyright 2024 OpenC3, Inc. +# Copyright 2026 OpenC3, Inc. # All Rights Reserved. # # This program is free software; you can modify and/or redistribute it @@ -34,6 +34,10 @@ def __init__(self, *coeffs): # @param (see Conversion#call) # @return [Float] The value with the polynomial applied def call(self, value, myself, buffer): + # Return None if value is None (item outside buffer bounds) + if value is None: + return None + value = float(value) # Handle C0 diff --git a/openc3/python/openc3/conversions/segmented_polynomial_conversion.py b/openc3/python/openc3/conversions/segmented_polynomial_conversion.py index 96e45f08cc..0231e4669d 100644 --- a/openc3/python/openc3/conversions/segmented_polynomial_conversion.py +++ b/openc3/python/openc3/conversions/segmented_polynomial_conversion.py @@ -1,4 +1,4 @@ -# Copyright 2023 OpenC3, Inc. +# Copyright 2026 OpenC3, Inc. # All Rights Reserved. # # This program is free software; you can modify and/or redistribute it @@ -92,6 +92,10 @@ def add_segment(self, lower_bound, *coeffs): # @param (see Conversion#call) # @return [Float] The value with the polynomial applied def call(self, value, packet, buffer): + # Return None if value is None (item outside buffer bounds) + if value is None: + return None + # Try to find correct segment for segment in self.segments: if value >= segment.lower_bound: diff --git a/openc3/python/openc3/packets/packet.py b/openc3/python/openc3/packets/packet.py index f491e718fb..37019022c1 100644 --- a/openc3/python/openc3/packets/packet.py +++ b/openc3/python/openc3/packets/packet.py @@ -1,4 +1,4 @@ -# Copyright 2025 OpenC3, Inc. +# Copyright 2026 OpenC3, Inc. # All Rights Reserved. # # This program is free software; you can modify and/or redistribute it @@ -980,6 +980,10 @@ def check_limits(self, limits_set="DEFAULT", ignore_persistence=False): if item.limits.enabled: value = self.read_item(item) + # Skip limits checking if value is None (item outside buffer bounds) + if value is None: + continue + # Handle state monitoring and value monitoring differently if item.states is not None: self.handle_limits_states(item, value) @@ -1313,6 +1317,10 @@ def handle_limits_values(self, item, value, limits_set, ignore_persistence): item.limits.persistence_count = 0 def apply_format_string_and_units(self, item, value, value_type): + # Return None as-is - can't format a value that doesn't exist + if value is None: + return None + if value_type == "FORMATTED" or value_type == "WITH_UNITS": if item.format_string and value is not None: value = f"{item.format_string}" % value diff --git a/openc3/python/openc3/packets/packet_config.py b/openc3/python/openc3/packets/packet_config.py index 1225032b37..7a3cc0c2d1 100644 --- a/openc3/python/openc3/packets/packet_config.py +++ b/openc3/python/openc3/packets/packet_config.py @@ -595,8 +595,8 @@ def process_current_packet(self, parser, keyword, params): ): self.start_item(parser) - # Allow this packet to be received with less data than the defined length - # without generating a warning. + # Allow this packet to be received with less data than the defined length. + # Items that are beyond the buffer will return None when read. case "ALLOW_SHORT": usage = keyword parser.verify_num_parameters(0, 0, usage) diff --git a/openc3/python/openc3/packets/structure.py b/openc3/python/openc3/packets/structure.py index d73bb47598..cc73214ae9 100644 --- a/openc3/python/openc3/packets/structure.py +++ b/openc3/python/openc3/packets/structure.py @@ -1,4 +1,4 @@ -# Copyright 2025 OpenC3, Inc. +# Copyright 2026 OpenC3, Inc. # All Rights Reserved. # # This program is free software; you can modify and/or redistribute it @@ -327,7 +327,12 @@ def set_item(self, item): item.variable_bit_size["length_value_bit_offset"] * item.variable_bit_size["length_bits_per_count"] ) - if minimum_data_bits > 0 and item.bit_offset >= 0 and self.defined_length_bits == item.bit_offset and item.parent_item is None: + if ( + minimum_data_bits > 0 + and item.bit_offset >= 0 + and self.defined_length_bits == item.bit_offset + and item.parent_item is None + ): self.defined_length_bits += minimum_data_bits else: raise ValueError(f"Unknown item: {item.name} - Ensure item name is uppercase") @@ -605,7 +610,9 @@ def internal_buffer_equals(self, buffer): if self.accessor.enforce_length(): if len(self._buffer) != self.defined_length: if len(self._buffer) < self.defined_length: - self.resize_buffer() + # Only resize if short_buffer_allowed is false + # When short_buffer_allowed is true, keep the buffer short so reads + # of items beyond the buffer return None if not self.short_buffer_allowed: raise ValueError("Buffer length less than defined length") elif self.fixed_size and self.defined_length != 0: diff --git a/openc3/python/poetry.lock b/openc3/python/poetry.lock index e9ea60685f..93f1ae7028 100644 --- a/openc3/python/poetry.lock +++ b/openc3/python/poetry.lock @@ -83,18 +83,18 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "boto3" -version = "1.42.26" +version = "1.42.28" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "boto3-1.42.26-py3-none-any.whl", hash = "sha256:f116cfbe7408e0a9153da363f134d2f1b5008f17ee86af104f0ce59a62be1833"}, - {file = "boto3-1.42.26.tar.gz", hash = "sha256:0fbcf1922e62d180f3644bc1139425821b38d93c1e6ec27409325d2ae86131aa"}, + {file = "boto3-1.42.28-py3-none-any.whl", hash = "sha256:7994bc2a094c1894f6a4221a1696c5d18af6c9c888191051866f1d05c4fba431"}, + {file = "boto3-1.42.28.tar.gz", hash = "sha256:7d56c298b8d98f5e9b04cf5d6627f68e7792e25614533aef17f815681b5e1096"}, ] [package.dependencies] -botocore = ">=1.42.26,<1.43.0" +botocore = ">=1.42.28,<1.43.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.16.0,<0.17.0" @@ -103,14 +103,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.42.26" +version = "1.42.28" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "botocore-1.42.26-py3-none-any.whl", hash = "sha256:71171c2d09ac07739f4efce398b15a4a8bc8769c17fb3bc99625e43ed11ad8b7"}, - {file = "botocore-1.42.26.tar.gz", hash = "sha256:1c8855e3e811f015d930ccfe8751d4be295aae0562133d14b6f0b247cd6fd8d3"}, + {file = "botocore-1.42.28-py3-none-any.whl", hash = "sha256:d26c7a0851489ce1a18279f9802fe434bd736ea861d4888cc2c7d83fb1f6af8f"}, + {file = "botocore-1.42.28.tar.gz", hash = "sha256:0c15e78d1accf97df691083331f682e97b1bef73ef12dcdaadcf652abf9c182c"}, ] [package.dependencies] diff --git a/openc3/python/test/accessors/test_binary_accessor_read.py b/openc3/python/test/accessors/test_binary_accessor_read.py index b145aba468..63adde6039 100644 --- a/openc3/python/test/accessors/test_binary_accessor_read.py +++ b/openc3/python/test/accessors/test_binary_accessor_read.py @@ -1,4 +1,4 @@ -# Copyright 2023 OpenC3, Inc. +# Copyright 2026 OpenC3, Inc. # All Rights Reserved. # # This program is free software; you can modify and/or redistribute it @@ -191,17 +191,8 @@ def test_complains_about_unaligned_blocks(self): "BIG_ENDIAN", ) - def test_complains_if_read_exceeds_the_size_of_the_buffer(self): - self.assertRaisesRegex( - ValueError, - "16 byte buffer insufficient to read STRING at bit_offset 8 with bit_size 800", - BinaryAccessor.read, - 8, - 800, - "STRING", - self.data, - "BIG_ENDIAN", - ) + def test_returns_none_if_read_exceeds_the_size_of_the_buffer(self): + self.assertIsNone(BinaryAccessor.read(8, 800, "STRING", self.data, "BIG_ENDIAN")) def test_reads_aligned_8_bit_unsigned_integers(self): for bit_offset in range(0, (len(self.data) - 1) * 8, 8): @@ -949,12 +940,8 @@ def test_complains_with_negative_array_size(self): ): BinaryAccessor.read_array(-32, 8, "UINT", -8, self.data, "LITTLE_ENDIAN") - def test_complains_about_accessing_data_from_a_buffer_which_is_too_small(self): - with self.assertRaisesRegex( - ValueError, - "16 byte buffer insufficient to read STRING at bit_offset 0 with bit_size 256", - ): - BinaryAccessor.read_array(0, 256, "STRING", 256, self.data, "LITTLE_ENDIAN") + def test_returns_none_for_accessing_data_from_a_buffer_which_is_too_small(self): + self.assertIsNone(BinaryAccessor.read_array(0, 256, "STRING", 256, self.data, "LITTLE_ENDIAN")) def test_returns_an_empty_array_when_passed_a_zero_length_buffer(self): self.assertEqual(BinaryAccessor.read_array(0, 8, "UINT", 32, b"", "LITTLE_ENDIAN"), []) @@ -1114,3 +1101,23 @@ def test_reads_aligned_64_bit_floats(self): actual = BinaryAccessor.read_array(0, 64, "FLOAT", 0, self.data, "BIG_ENDIAN") for index, val in enumerate(actual): self.assertAlmostEqual(val, expected_array[index]) + + +class TestBinaryAccessorReadItemUndersizedBuffer(unittest.TestCase): + def test_returns_none_for_items_outside_buffer_bounds(self): + from openc3.packets.structure_item import StructureItem + buffer = bytearray(b"\x00\x01\x02\x03") # 4 bytes + item = StructureItem("TEST", 32, 32, "UINT", "BIG_ENDIAN") + self.assertIsNone(BinaryAccessor.class_read_item(item, buffer)) + + def test_returns_value_for_items_within_buffer_bounds(self): + from openc3.packets.structure_item import StructureItem + buffer = bytearray(b"\x00\x01\x02\x03") # 4 bytes + item = StructureItem("TEST", 0, 32, "UINT", "BIG_ENDIAN") + self.assertEqual(BinaryAccessor.class_read_item(item, buffer), 0x00010203) + + def test_returns_none_for_partially_out_of_bounds_items(self): + from openc3.packets.structure_item import StructureItem + buffer = bytearray(b"\x00\x01\x02\x03") # 4 bytes = 32 bits + item = StructureItem("TEST", 16, 32, "UINT", "BIG_ENDIAN") + self.assertIsNone(BinaryAccessor.class_read_item(item, buffer)) diff --git a/openc3/python/test/packets/parsers/test_limits_parser.py b/openc3/python/test/packets/parsers/test_limits_parser.py index 80b4f5b102..52f5413f7e 100644 --- a/openc3/python/test/packets/parsers/test_limits_parser.py +++ b/openc3/python/test/packets/parsers/test_limits_parser.py @@ -1,4 +1,4 @@ -# Copyright 2023 OpenC3, Inc. +# Copyright 2026 OpenC3, Inc. # All Rights Reserved. # # This program is free software; you can modify and/or redistribute it @@ -316,7 +316,7 @@ def test_takes_4_limits_values(self): self.pc.process_file(tf.name, "TGT1") item = self.pc.telemetry["TGT1"]["PKT1"].items["ITEM1"] self.assertIsNotNone(item.limits.values["DEFAULT"]) - self.pc.telemetry["TGT1"]["PKT1"].buffer = b"\x04" + self.pc.telemetry["TGT1"]["PKT1"].buffer = b"\x04\x00" self.pc.telemetry["TGT1"]["PKT1"].enable_limits("ITEM1") self.pc.telemetry["TGT1"]["PKT1"].check_limits() self.assertEqual(item.limits.state, "GREEN") @@ -332,7 +332,7 @@ def test_takes_6_limits_values(self): self.pc.process_file(tf.name, "TGT1") item = self.pc.telemetry["TGT1"]["PKT1"].items["ITEM1"] self.assertIsNotNone(item.limits.values["DEFAULT"]) - self.pc.telemetry["TGT1"]["PKT1"].buffer = b"\x04" + self.pc.telemetry["TGT1"]["PKT1"].buffer = b"\x04\x00" self.pc.telemetry["TGT1"]["PKT1"].enable_limits("ITEM1") self.pc.telemetry["TGT1"]["PKT1"].check_limits() self.assertEqual(item.limits.state, "BLUE") diff --git a/openc3/python/test/packets/test_commands.py b/openc3/python/test/packets/test_commands.py index 136a682e4d..8c29f25e94 100644 --- a/openc3/python/test/packets/test_commands.py +++ b/openc3/python/test/packets/test_commands.py @@ -1,4 +1,4 @@ -# Copyright 2024 OpenC3, Inc. +# Copyright 2026 OpenC3, Inc. # All Rights Reserved. # # This program is free software; you can modify and/or redistribute it @@ -181,7 +181,7 @@ def test_identify_logs_an_invalid_sized_buffer1(self): self.assertEqual(pkt.read("item1"), 1) self.assertEqual(pkt.read("item2"), 2) self.assertEqual(pkt.read("item3"), 3) - self.assertEqual(pkt.read("item4"), 0) + self.assertIsNone(pkt.read("item4")) self.assertIn( "TGT1 PKT1 buffer () received with actual packet length of 3 but defined length of 4", stdout.getvalue(), diff --git a/openc3/python/test/packets/test_packet.py b/openc3/python/test/packets/test_packet.py index 6cebee20bb..2be0d11c89 100644 --- a/openc3/python/test/packets/test_packet.py +++ b/openc3/python/test/packets/test_packet.py @@ -1,4 +1,4 @@ -# Copyright 2025 OpenC3, Inc. +# Copyright 2026 OpenC3, Inc. # All Rights Reserved. # # This program is free software; you can modify and/or redistribute it @@ -1980,7 +1980,7 @@ def test_obfuscates_array_string_items(self): i = p.get_item("TEST1") i.obfuscate = True p.update_obfuscated_items_cache(i) - p.buffer = b"TESTTESTEST" + p.buffer = b"TESTTESTTEST" p.obfuscate() self.assertEqual(p.buffer, b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") diff --git a/openc3/python/test/packets/test_packet_config.py b/openc3/python/test/packets/test_packet_config.py index 1b21ce55a6..c18337b571 100644 --- a/openc3/python/test/packets/test_packet_config.py +++ b/openc3/python/test/packets/test_packet_config.py @@ -1,4 +1,4 @@ -# Copyright 2025 OpenC3, Inc. +# Copyright 2026 OpenC3, Inc. # All Rights Reserved. # # This program is free software; you can modify and/or redistribute it @@ -1011,7 +1011,7 @@ def test_parses_the_conversion(self): tf.write(" READ_CONVERSION openc3/test_real_conversion.py\n") tf.seek(0) self.pc.process_file(tf.name, "TGT1") - self.pc.telemetry["TGT1"]["PKT1"].buffer = b"\x01" + self.pc.telemetry["TGT1"]["PKT1"].buffer = b"\x01\x00" self.assertEqual(self.pc.telemetry["TGT1"]["PKT1"].read("ITEM1"), 2) tf.close() @@ -1033,7 +1033,7 @@ def test_performs_a_polynomial_conversion(self): tf.write(" POLY_READ_CONVERSION 5 2\n") tf.seek(0) self.pc.process_file(tf.name, "TGT1") - self.pc.telemetry["TGT1"]["PKT1"].buffer = b"\x01" + self.pc.telemetry["TGT1"]["PKT1"].buffer = b"\x01\x00" self.assertEqual(self.pc.telemetry["TGT1"]["PKT1"].read("ITEM1"), 7.0) tf.close() @@ -1055,9 +1055,9 @@ def test_performs_a_segmented_polynomial_conversion(self): tf.write(" SEG_POLY_READ_CONVERSION 5 2 3\n") tf.seek(0) self.pc.process_file(tf.name, "TGT1") - self.pc.telemetry["TGT1"]["PKT1"].buffer = b"\x01" + self.pc.telemetry["TGT1"]["PKT1"].buffer = b"\x01\x00" self.assertEqual(self.pc.telemetry["TGT1"]["PKT1"].read("ITEM1"), 3.0) - self.pc.telemetry["TGT1"]["PKT1"].buffer = b"\x05" + self.pc.telemetry["TGT1"]["PKT1"].buffer = b"\x05\x00" self.assertEqual(self.pc.telemetry["TGT1"]["PKT1"].read("ITEM1"), 17.0) tf.close() @@ -1141,7 +1141,7 @@ def test_ensures_limits_sets_have_unique_names(self): item = self.pc.telemetry["TGT1"]["PKT1"].items["ITEM1"] self.assertEqual(len(item.limits.values), 2) # Verify the last defined DEFAULT limits wins - self.pc.telemetry["TGT1"]["PKT1"].buffer = b"\x04" + self.pc.telemetry["TGT1"]["PKT1"].buffer = b"\x04\x00" self.pc.telemetry["TGT1"]["PKT1"].enable_limits("ITEM1") self.pc.telemetry["TGT1"]["PKT1"].check_limits() self.assertEqual(item.limits.state, "RED_LOW") diff --git a/openc3/python/test/packets/test_structure.py b/openc3/python/test/packets/test_structure.py index 926fe753ef..db755675a5 100644 --- a/openc3/python/test/packets/test_structure.py +++ b/openc3/python/test/packets/test_structure.py @@ -1129,3 +1129,28 @@ def test_recalculates_the_bit_offsets_for_0_size(self): self.assertEqual(s.read("test1"), b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09") self.assertEqual(s.read("test2"), b"\x0a\x0b\x0c\x0d\x0e\x0f\x0f\x0e\x0d\x0c\x0b\x0a\xaa\x55") self.assertEqual(s.read("test3"), 0xAA55) + + +class TestStructureShortBufferAllowed(unittest.TestCase): + def test_returns_none_for_items_outside_buffer_bounds(self): + s = Structure("BIG_ENDIAN") + s.append_item("item1", 16, "UINT") + s.append_item("item2", 16, "UINT") + s.append_item("item3", 16, "UINT") + s.short_buffer_allowed = True + # Set a short buffer that only contains data for item1 + s.buffer = b"\x00\x01" + # item1 should read successfully + self.assertEqual(s.read("item1"), 1) + # item2 and item3 should return None since they're outside the buffer + self.assertIsNone(s.read("item2")) + self.assertIsNone(s.read("item3")) + # Buffer should remain at its original size (not padded) + self.assertEqual(len(s.buffer), 2) + + def test_raises_error_when_short_buffer_allowed_is_false(self): + s = Structure("BIG_ENDIAN") + s.append_item("item1", 16, "UINT") + s.append_item("item2", 16, "UINT") + with self.assertRaisesRegex(ValueError, "Buffer length less than defined length"): + s.buffer = b"\x00\x01" diff --git a/openc3/spec/accessors/binary_accessor_spec.rb b/openc3/spec/accessors/binary_accessor_spec.rb index 50f9a8de4d..05ffe74e03 100644 --- a/openc3/spec/accessors/binary_accessor_spec.rb +++ b/openc3/spec/accessors/binary_accessor_spec.rb @@ -14,7 +14,7 @@ # GNU Affero General Public License for more details. # Modified by OpenC3, Inc. -# All changes Copyright 2022, OpenC3, Inc. +# All changes Copyright 2026, OpenC3, Inc. # All Rights Reserved # # This file may also be used under the terms of a commercial license @@ -24,6 +24,7 @@ require 'openc3' require 'openc3/accessors/binary_accessor' require 'openc3/packets/packet' +require 'openc3/packets/structure_item' module OpenC3 describe BinaryAccessor, no_ext: true do @@ -365,8 +366,8 @@ module OpenC3 expect { BinaryAccessor.read(7, 16, :BLOCK, @data, :BIG_ENDIAN) }.to raise_error(ArgumentError, "bit_offset 7 is not byte aligned for data_type BLOCK") end - it "complains if read exceeds the size of the buffer" do - expect { BinaryAccessor.read(8, 800, :STRING, @data, :BIG_ENDIAN) }.to raise_error(ArgumentError, "16 byte buffer insufficient to read STRING at bit_offset 8 with bit_size 800") + it "returns nil if read exceeds the size of the buffer" do + expect(BinaryAccessor.read(8, 800, :STRING, @data, :BIG_ENDIAN)).to be_nil end it "reads aligned 8-bit unsigned integers" do @@ -814,12 +815,12 @@ module OpenC3 end end - it "complains about accessing data from a buffer which is too small" do - expect { BinaryAccessor.read_array(0, 256, :STRING, 256, @data, :LITTLE_ENDIAN) }.to raise_error(ArgumentError, "16 byte buffer insufficient to read STRING at bit_offset 0 with bit_size 256") + it "returns nil for accessing data from a buffer which is too small" do + expect(BinaryAccessor.read_array(0, 256, :STRING, 256, @data, :LITTLE_ENDIAN)).to be_nil end - it "returns an empty array when passed a zero length buffer" do - expect(BinaryAccessor.read_array(0, 8, :UINT, 32, "", :LITTLE_ENDIAN)).to eql([]) + it "returns nil when reading beyond an empty buffer" do + expect(BinaryAccessor.read_array(0, 8, :UINT, 32, "", :LITTLE_ENDIAN)).to be_nil end it "complains about unaligned strings" do @@ -2564,5 +2565,25 @@ module OpenC3 end end end # describe "write_array" + + describe "read_item with undersized buffer" do + it "returns nil for items outside buffer bounds" do + buffer = "\x00\x01\x02\x03" # 4 bytes + item = StructureItem.new("TEST", 32, 32, :UINT, :BIG_ENDIAN) + expect(BinaryAccessor.read_item(item, buffer)).to be_nil + end + + it "returns value for items within buffer bounds" do + buffer = "\x00\x01\x02\x03" # 4 bytes + item = StructureItem.new("TEST", 0, 32, :UINT, :BIG_ENDIAN) + expect(BinaryAccessor.read_item(item, buffer)).to eq(0x00010203) + end + + it "returns nil for partially out-of-bounds items" do + buffer = "\x00\x01\x02\x03" # 4 bytes = 32 bits + item = StructureItem.new("TEST", 16, 32, :UINT, :BIG_ENDIAN) + expect(BinaryAccessor.read_item(item, buffer)).to be_nil + end + end end # describe BinaryAccessor end diff --git a/openc3/spec/packets/commands_spec.rb b/openc3/spec/packets/commands_spec.rb index 9c3b9a19f2..eeda324c09 100644 --- a/openc3/spec/packets/commands_spec.rb +++ b/openc3/spec/packets/commands_spec.rb @@ -14,7 +14,7 @@ # GNU Affero General Public License for more details. # Modified by OpenC3, Inc. -# All changes Copyright 2025, OpenC3, Inc. +# All changes Copyright 2026, OpenC3, Inc. # All Rights Reserved # # This file may also be used under the terms of a commercial license @@ -213,7 +213,7 @@ module OpenC3 expect(pkt.item1).to eql 1 expect(pkt.item2).to eql 2 expect(pkt.item3).to eql 3 - expect(pkt.item4).to eql 0 + expect(pkt.item4).to be_nil expect(stdout.string).to match(/TGT1 PKT1 received with actual packet length of 3 but defined length of 4/) end end diff --git a/openc3/spec/packets/packet_config_spec.rb b/openc3/spec/packets/packet_config_spec.rb index 05886effd3..4afbfaba00 100644 --- a/openc3/spec/packets/packet_config_spec.rb +++ b/openc3/spec/packets/packet_config_spec.rb @@ -14,7 +14,7 @@ # GNU Affero General Public License for more details. # Modified by OpenC3, Inc. -# All changes Copyright 2025, OpenC3, Inc. +# All changes Copyright 2026, OpenC3, Inc. # All Rights Reserved # # This file may also be used under the terms of a commercial license @@ -872,7 +872,7 @@ def call(packet) tf.puts ' READ_CONVERSION conversion2.rb' tf.close @pc.process_file(tf.path, "TGT1") - @pc.telemetry["TGT1"]["PKT1"].buffer = "\x01" + @pc.telemetry["TGT1"]["PKT1"].buffer = "\x01\x00" expect(@pc.telemetry["TGT1"]["PKT1"].read("ITEM1")).to eql 2 tf.unlink @@ -898,7 +898,7 @@ def call(packet) tf.puts ' POLY_READ_CONVERSION 5 2' tf.close @pc.process_file(tf.path, "TGT1") - @pc.telemetry["TGT1"]["PKT1"].buffer = "\x01" + @pc.telemetry["TGT1"]["PKT1"].buffer = "\x01\x00" expect(@pc.telemetry["TGT1"]["PKT1"].read("ITEM1")).to eql 7.0 tf.unlink @@ -923,9 +923,9 @@ def call(packet) tf.puts ' SEG_POLY_READ_CONVERSION 5 2 3' tf.close @pc.process_file(tf.path, "TGT1") - @pc.telemetry["TGT1"]["PKT1"].buffer = "\x01" + @pc.telemetry["TGT1"]["PKT1"].buffer = "\x01\x00" expect(@pc.telemetry["TGT1"]["PKT1"].read("ITEM1")).to eql 3.0 - @pc.telemetry["TGT1"]["PKT1"].buffer = "\x05" + @pc.telemetry["TGT1"]["PKT1"].buffer = "\x05\x00" expect(@pc.telemetry["TGT1"]["PKT1"].read("ITEM1")).to eql 17.0 tf.unlink @@ -1002,7 +1002,7 @@ def call(packet) item = @pc.telemetry["TGT1"]["PKT1"].items["ITEM1"] expect(item.limits.values.length).to eql 2 # Verify the last defined DEFAULT limits wins - @pc.telemetry["TGT1"]["PKT1"].buffer = "\x04" + @pc.telemetry["TGT1"]["PKT1"].buffer = "\x04\x00" @pc.telemetry["TGT1"]["PKT1"].enable_limits("ITEM1") @pc.telemetry["TGT1"]["PKT1"].check_limits expect(item.limits.state).to eql :RED_LOW diff --git a/openc3/spec/packets/packet_spec.rb b/openc3/spec/packets/packet_spec.rb index 993fb17cd1..91976bd291 100644 --- a/openc3/spec/packets/packet_spec.rb +++ b/openc3/spec/packets/packet_spec.rb @@ -14,7 +14,7 @@ # GNU Affero General Public License for more details. # # Modified by OpenC3, Inc. -# All changes Copyright 2025, OpenC3, Inc. +# All changes Copyright 2026, OpenC3, Inc. # All Rights Reserved # # This file may also be used under the terms of a commercial license @@ -594,15 +594,15 @@ module OpenC3 expect(@p.read_item(i, :WITH_UNITS, "\x00\x01")).to eql ["0x0 V", "TRUE"] expect(@p.read("ITEM", :WITH_UNITS, "\x02\x03")).to eql ["FALSE", "0x3 V"] expect(@p.read_item(i, :WITH_UNITS, "\x02\x03")).to eql ["FALSE", "0x3 V"] - expect(@p.read("ITEM", :WITH_UNITS, "\x04")).to eql ["0x4 V"] - expect(@p.read_item(i, :WITH_UNITS, "\x04")).to eql ["0x4 V"] - expect(@p.read("ITEM", :WITH_UNITS, "\x04")).to eql ["0x4 V"] - expect(@p.read_item(i, :WITH_UNITS, "\x04")).to eql ["0x4 V"] + expect(@p.read("ITEM", :WITH_UNITS, "\x04\x00")).to eql ["0x4 V", "0x0 V"] + expect(@p.read_item(i, :WITH_UNITS, "\x04\x00")).to eql ["0x4 V", "0x0 V"] + expect(@p.read("ITEM", :WITH_UNITS, "\x04\x00")).to eql ["0x4 V", "0x0 V"] + expect(@p.read_item(i, :WITH_UNITS, "\x04\x00")).to eql ["0x4 V", "0x0 V"] i.read_conversion = GenericConversion.new("value / 2") expect(@p.read("ITEM", :WITH_UNITS, "\x02\x04")).to eql ["TRUE", "FALSE"] expect(@p.read_item(i, :WITH_UNITS, "\x02\x04")).to eql ["TRUE", "FALSE"] - expect(@p.read("ITEM", :WITH_UNITS, "\x08")).to eql ["0x4 V"] - expect(@p.read_item(i, :WITH_UNITS, "\x08")).to eql ["0x4 V"] + expect(@p.read("ITEM", :WITH_UNITS, "\x08\x00")).to eql ["0x4 V", "0x0 V"] + expect(@p.read_item(i, :WITH_UNITS, "\x08\x00")).to eql ["0x4 V", "0x0 V"] @p.define_item("item2", 0, 0, :DERIVED) i = @p.get_item("ITEM2") i.units = "V" diff --git a/openc3/spec/packets/parsers/limits_parser_spec.rb b/openc3/spec/packets/parsers/limits_parser_spec.rb index efbe1865ac..6b563b605c 100644 --- a/openc3/spec/packets/parsers/limits_parser_spec.rb +++ b/openc3/spec/packets/parsers/limits_parser_spec.rb @@ -14,7 +14,7 @@ # GNU Affero General Public License for more details. # Modified by OpenC3, Inc. -# All changes Copyright 2022, OpenC3, Inc. +# All changes Copyright 2026, OpenC3, Inc. # All Rights Reserved # # This file may also be used under the terms of a commercial license @@ -261,7 +261,7 @@ module OpenC3 @pc.process_file(tf.path, "TGT1") item = @pc.telemetry["TGT1"]["PKT1"].items["ITEM1"] expect(item.limits.values[:DEFAULT]).not_to be_nil - @pc.telemetry["TGT1"]["PKT1"].buffer = "\x04" + @pc.telemetry["TGT1"]["PKT1"].buffer = "\x04\x00" @pc.telemetry["TGT1"]["PKT1"].enable_limits("ITEM1") @pc.telemetry["TGT1"]["PKT1"].check_limits expect(item.limits.state).to eql :GREEN @@ -278,7 +278,7 @@ module OpenC3 @pc.process_file(tf.path, "TGT1") item = @pc.telemetry["TGT1"]["PKT1"].items["ITEM1"] expect(item.limits.values[:DEFAULT]).not_to be_nil - @pc.telemetry["TGT1"]["PKT1"].buffer = "\x04" + @pc.telemetry["TGT1"]["PKT1"].buffer = "\x04\x00" @pc.telemetry["TGT1"]["PKT1"].enable_limits("ITEM1") @pc.telemetry["TGT1"]["PKT1"].check_limits expect(item.limits.state).to eql :BLUE diff --git a/openc3/spec/packets/structure_spec.rb b/openc3/spec/packets/structure_spec.rb index 6b2242d428..048f321127 100644 --- a/openc3/spec/packets/structure_spec.rb +++ b/openc3/spec/packets/structure_spec.rb @@ -892,5 +892,31 @@ module OpenC3 s.set_item(item) end end + + describe "short_buffer_allowed" do + it "returns nil for items outside buffer bounds when short_buffer_allowed is true" do + s = Structure.new(:BIG_ENDIAN) + s.append_item("item1", 16, :UINT) + s.append_item("item2", 16, :UINT) + s.append_item("item3", 16, :UINT) + s.short_buffer_allowed = true + # Set a short buffer that only contains data for item1 + s.buffer = "\x00\x01" + # item1 should read successfully + expect(s.read("item1")).to eq(1) + # item2 and item3 should return nil since they're outside the buffer + expect(s.read("item2")).to be_nil + expect(s.read("item3")).to be_nil + # Buffer should remain at its original size (not padded) + expect(s.buffer.length).to eq(2) + end + + it "raises error when short_buffer_allowed is false" do + s = Structure.new(:BIG_ENDIAN) + s.append_item("item1", 16, :UINT) + s.append_item("item2", 16, :UINT) + expect { s.buffer = "\x00\x01" }.to raise_error(RuntimeError, /Buffer length less than defined length/) + end + end end # describe Structure end