Skip to content

Commit c68ea36

Browse files
author
Ryan Fox
committed
Add a "number of elements" provider for FieldArray, allowing for arrays to be specified with a number of elements instead of just a size in bytes.
1 parent 5931d36 commit c68ea36

File tree

2 files changed

+36
-8
lines changed

2 files changed

+36
-8
lines changed

suitcase/fields.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,8 @@ class FieldArray(BaseField):
845845
the entire contents of the array.
846846
:param length_provider: The field providing a length value binding this
847847
message (if any). Set this field to None to leave unconstrained.
848+
:param num_elements_provider: The field providing the number of elements
849+
in the array. Set this field to None to leave unconstrained.
848850
849851
For example, one can imagine a message listing the zipcodes covered by
850852
a telephone area code. Depending on the population density, the number
@@ -863,7 +865,7 @@ class TelephoneZipcodes(Structure):
863865
864866
"""
865867

866-
def __init__(self, substructure, length_provider=None, **kwargs):
868+
def __init__(self, substructure, length_provider=None, num_elements_provider=None, **kwargs):
867869
BaseField.__init__(self, **kwargs)
868870
self.substructure = substructure
869871
self._value = list()
@@ -872,6 +874,12 @@ def __init__(self, substructure, length_provider=None, **kwargs):
872874
self.length_provider.associate_length_consumer(self)
873875
else:
874876
self.length_provider = None
877+
if isinstance(num_elements_provider, FieldPlaceholder):
878+
self.num_elements_provider = self._ph2f(num_elements_provider)
879+
self.num_elements_provider.length_value_provider = lambda: len(self._value)
880+
else:
881+
self.num_elements_provider = None
882+
875883

876884
@property
877885
def bytes_required(self):
@@ -880,15 +888,22 @@ def bytes_required(self):
880888
else:
881889
return self.length_provider.get_adjusted_length()
882890

891+
@property
892+
def num_elements(self):
893+
if self.num_elements_provider is None:
894+
return None
895+
else:
896+
return self.num_elements_provider.get_adjusted_length()
897+
883898
def pack(self, stream):
884899
for structure in self._value:
885900
stream.write(structure.pack())
886901

887902
def unpack(self, data, **kwargs):
888903
length = self.bytes_required
889-
if length == 0 or (data == b"" and length is None):
904+
if length == 0 or (data == b"" and length is None) or self.num_elements == 0:
890905
# Array is empty.
891-
return
906+
return data
892907

893908
kwargs['trailing'] = True
894909
while True:
@@ -897,6 +912,14 @@ def unpack(self, data, **kwargs):
897912
self._value.append(structure)
898913
if data == b"":
899914
break
915+
if len(self._value) == self.num_elements:
916+
break
917+
918+
if self.num_elements is not None and len(self._value) != self.num_elements:
919+
raise SuitcaseParseError("Expected %r elements but received %r." %
920+
(self.num_elements, len(self._value)))
921+
922+
return data
900923

901924

902925
class BaseFixedByteSequence(BaseField):

suitcase/structure.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
#
55
# Copyright (c) 2015 Digi International Inc. All Rights Reserved.
66
import sys
7-
7+
import os
88
import six
99
from suitcase.exceptions import SuitcaseException, \
1010
SuitcasePackException, SuitcaseParseError
11-
from suitcase.fields import FieldPlaceholder, CRCField, SubstructureField, \
11+
from suitcase.fields import FieldArray, FieldPlaceholder, CRCField, SubstructureField, \
1212
ConditionalField
1313
from six import BytesIO
1414

@@ -105,8 +105,12 @@ def unpack_stream(self, stream):
105105
stream.seek(stream.tell() + consumed)
106106
continue
107107
elif length is None:
108-
greedy_field = field
109-
break
108+
if isinstance(field, FieldArray) and field.num_elements is not None:
109+
# Read the data greedily now, and we'll backtrack after enough elements have been read.
110+
data = stream.read()
111+
else:
112+
greedy_field = field
113+
break
110114
else:
111115
data = stream.read(length)
112116
if len(data) != length:
@@ -116,7 +120,8 @@ def unpack_stream(self, stream):
116120
(name, length, len(data)))
117121

118122
try:
119-
field.unpack(data)
123+
unused_data = field.unpack(data)
124+
stream.seek(-len(unused_data or ""), os.SEEK_CUR)
120125
except SuitcaseException:
121126
raise # just re-raise these
122127
except Exception:

0 commit comments

Comments
 (0)