New ways to instantate cstruct objects.
"default" constructor zero-initializes the whole struct. (It's a common
pattern that should be easier to read after this.
keyword constructor so that instantiating a struct with many fields is
easier to read.
Change-Id: Ide18aa41c7289d63df7d0297673518fcafdf0174
Test: coming soon
diff --git a/net/test/cstruct.py b/net/test/cstruct.py
index 43c47a2..d31979e 100644
--- a/net/test/cstruct.py
+++ b/net/test/cstruct.py
@@ -25,7 +25,8 @@
>>> NLMsgHdr = cstruct.Struct("NLMsgHdr", "=LHHLL", "length type flags seq pid")
>>>
>>>
->>> # Create instances from tuples or raw bytes. Data past the end is ignored.
+>>> # Create instances from a tuple of values, raw bytes, zero-initialized, or
+>>> # using keywords.
... n1 = NLMsgHdr((44, 32, 0x2, 0, 491))
>>> print n1
NLMsgHdr(length=44, type=32, flags=2, seq=0, pid=491)
@@ -35,6 +36,14 @@
>>> print n2
NLMsgHdr(length=44, type=33, flags=2, seq=0, pid=510)
>>>
+>>> n3 = netlink.NLMsgHdr() # Zero-initialized
+>>> print n3
+NLMsgHdr(length=0, type=0, flags=0, seq=0, pid=0)
+>>>
+>>> n4 = netlink.NLMsgHdr(length=44, type=33) # Other fields zero-initialized
+>>> print n4
+NLMsgHdr(length=44, type=33, flags=0, seq=0, pid=0)
+>>>
>>> # Serialize to raw bytes.
... print n1.Pack().encode("hex")
2c0000002000020000000000eb010000
@@ -123,12 +132,14 @@
_asciiz.add(index)
_format += "s"
else:
- # Standard struct format character.
+ # Standard struct format character.
_format += fmt[i]
_length = CalcSize(_format)
def _SetValues(self, values):
+ # Replace self._values with the given list. We can't do direct assignment
+ # because of the __setattr__ overload on this class.
super(CStruct, self).__setattr__("_values", list(values))
def _Parse(self, data):
@@ -139,19 +150,37 @@
values[index] = self._nested[index](value)
self._SetValues(values)
- def __init__(self, values):
- # Initializing from a string.
- if isinstance(values, str):
- if len(values) < self._length:
+ def __init__(self, tuple_or_bytes=None, **kwargs):
+ """Construct an instance of this Struct.
+
+ 1. With no args, the whole struct is zero-initialized.
+ 2. With keyword args, the matching fields are populated; rest are zeroed.
+ 3. With one tuple as the arg, the fields are assigned based on position.
+ 4. With one string arg, the Struct is parsed from bytes.
+ """
+ if tuple_or_bytes and kwargs:
+ raise TypeError(
+ "%s: cannot specify both a tuple and keyword args" % self._name)
+
+ if tuple_or_bytes is None:
+ # Default construct from null bytes.
+ self._Parse("\x00" * len(self))
+ # If any keywords were supplied, set those fields.
+ for k, v in kwargs.iteritems():
+ setattr(self, k, v)
+ elif isinstance(tuple_or_bytes, str):
+ # Initializing from a string.
+ if len(tuple_or_bytes) < self._length:
raise TypeError("%s requires string of length %d, got %d" %
- (self._name, self._length, len(values)))
- self._Parse(values)
+ (self._name, self._length, len(tuple_or_bytes)))
+ self._Parse(tuple_or_bytes)
else:
# Initializing from a tuple.
- if len(values) != len(self._fieldnames):
+ if len(tuple_or_bytes) != len(self._fieldnames):
raise TypeError("%s has exactly %d fieldnames (%d given)" %
- (self._name, len(self._fieldnames), len(values)))
- self._SetValues(values)
+ (self._name, len(self._fieldnames),
+ len(tuple_or_bytes)))
+ self._SetValues(tuple_or_bytes)
def _FieldIndex(self, attr):
try:
@@ -164,6 +193,8 @@
return self._values[self._FieldIndex(name)]
def __setattr__(self, name, value):
+ # TODO: check value type against self._format and throw here, or else
+ # callers get an unhelpful exception when they call Pack().
self._values[self._FieldIndex(name)] = value
@classmethod
diff --git a/net/test/cstruct_test.py b/net/test/cstruct_test.py
index fdcbd55..b69aeb7 100755
--- a/net/test/cstruct_test.py
+++ b/net/test/cstruct_test.py
@@ -110,6 +110,33 @@
" int3=12345, ascii4=hello\x00visible123, word5=33210)")
self.assertEquals(expected, str(t))
+ def testZeroInitialization(self):
+ TestStruct = cstruct.Struct("TestStruct", "B16si16AH",
+ "byte1 string2 int3 ascii4 word5")
+ t = TestStruct()
+ self.assertEquals(0, t.byte1)
+ self.assertEquals("\x00" * 16, t.string2)
+ self.assertEquals(0, t.int3)
+ self.assertEquals("\x00" * 16, t.ascii4)
+ self.assertEquals(0, t.word5)
+ self.assertEquals("\x00" * len(TestStruct), t.Pack())
+
+ def testKeywordInitialization(self):
+ TestStruct = cstruct.Struct("TestStruct", "=B16sIH",
+ "byte1 string2 int3 word4")
+ text = "hello world! ^_^"
+ text_bytes = text.encode("hex")
+
+ # Populate all fields
+ t1 = TestStruct(byte1=1, string2=text, int3=0xFEDCBA98, word4=0x1234)
+ expected = ("01" + text_bytes + "98BADCFE" "3412").decode("hex")
+ self.assertEquals(expected, t1.Pack())
+
+ # Partially populated
+ t1 = TestStruct(string2=text, word4=0x1234)
+ expected = ("00" + text_bytes + "00000000" "3412").decode("hex")
+ self.assertEquals(expected, t1.Pack())
+
if __name__ == "__main__":
unittest.main()