Repository: megajanlott/cbor-decoder

Issue:
Handle/Decode tags
Major type 6 describes optional tagging of items
We should decide whether to decode or skip them


Optional Tagging states:
- [ ] Implement  TagInfo
- [ ] Implement  TagRead(n)

Additional info about states can be found here:
https://docs.google.com/document/d/1tvQJtJbYUcM2vI5H0RDukWqNYsLxjczZ30PiBFFVsV8/edit#

Comments:

Patch:
diff --git a/cbor/MajorType.py b/cbor/MajorType.py
index 2f43a8c..018d188 100644
--- a/cbor/MajorType.py
+++ b/cbor/MajorType.py
@@ -1,9 +1,10 @@
 from cbor.CBORStream import CBORStream
 from cbor.State import State
-from cbor.type.ByteString import ByteString
-from cbor.type.TextString import TextString
 from cbor.type.Array import ArrayInfo
+from cbor.type.ByteString import ByteString
 from cbor.type.Map import MapInfo
+from cbor.type.Tag import TagInfo
+from cbor.type.TextString import TextString
 
 MAJOR_TYPE_MASK = 0b11100000
 MAJOR_TYPE_SIZE = 3
@@ -29,5 +30,7 @@ class MajorType(State):
             return ArrayInfo()
         elif t == 5:
             return MapInfo()
+        elif t == 6:
+            return TagInfo()
 
         return
diff --git a/cbor/type/Array.py b/cbor/type/Array.py
index 28bb061..014cdfe 100644
--- a/cbor/type/Array.py
+++ b/cbor/type/Array.py
@@ -12,7 +12,7 @@ class ArrayInfo(cbor.State.State):
         if length == 0:
             handler(']')
         elif length < 24:
-            return [cbor.MajorType.MajorType(), ArrayRead(length)]
+            return [ArrayRead(length), cbor.MajorType.MajorType()]
         elif length == 24:
             return [ArrayLen(1)]
         elif length == 25:
@@ -22,7 +22,7 @@ class ArrayInfo(cbor.State.State):
         elif length == 27:
             return [ArrayLen(8)]
         elif length == 31:
-            return [cbor.MajorType.MajorType(), ArrayInf()]
+            return [ArrayInf(), cbor.MajorType.MajorType()]
         return []
 
 
@@ -37,7 +37,7 @@ class ArrayRead(cbor.State.State):
     def run(self, stream: cbor.CBORStream.CBORStream, handler):
         if self.n > 1:
             handler(',')
-            return [cbor.MajorType.MajorType(), ArrayRead(self.n - 1)]
+            return [ArrayRead(self.n - 1), cbor.MajorType.MajorType()]
         handler(']')
         return []
 
@@ -53,11 +53,11 @@ class ArrayLen(cbor.State.State):
     def run(self, stream: cbor.CBORStream.CBORStream, handler):
         info = stream.read(self.n)
         length = int.from_bytes(info, byteorder='big')
-        return [cbor.MajorType.MajorType(), ArrayRead(length)]
+        return [ArrayRead(length), cbor.MajorType.MajorType()]
 
 
 class ArrayInf(cbor.State.State):
 
     def run(self, stream: cbor.CBORStream.CBORStream, handler):
         handler(',')
-        return [cbor.MajorType.MajorType(), ArrayInf()]
+        return [ArrayInf(), cbor.MajorType.MajorType()]
diff --git a/cbor/type/Tag.py b/cbor/type/Tag.py
new file mode 100644
index 0000000..d0a1a27
--- /dev/null
+++ b/cbor/type/Tag.py
@@ -0,0 +1,32 @@
+import cbor.CBORStream
+import cbor.MajorType
+import cbor.State
+
+
+class TagInfo(cbor.State.State):
+
+    def run(self, stream: cbor.CBORStream.CBORStream, handler):
+        info = stream.read(1)
+        length = ord(info) & 0b00011111
+        if length == 24:
+            return [TagRead(1)]
+        elif length == 25:
+            return [TagRead(2)]
+        elif length == 26:
+            return [TagRead(4)]
+        elif length == 27:
+            return [TagRead(8)]
+        return [cbor.MajorType.MajorType()]
+
+
+class TagRead(cbor.State.State):
+
+    def __eq__(self, other):
+        return self.n == other.n
+
+    def __init__(self, n: int):
+        self.n = n
+
+    def run(self, stream: cbor.CBORStream.CBORStream, handler):
+        stream.read(self.n)
+        return [cbor.MajorType.MajorType()]


Test patch:
diff --git a/tests/test_Array.py b/tests/test_Array.py
index 2755ae0..61539fa 100644
--- a/tests/test_Array.py
+++ b/tests/test_Array.py
@@ -3,6 +3,7 @@ from cbor.MajorType import MajorType
 from cbor.CBORStream import CBORStream
 from cbor.type.Array import ArrayInfo, ArrayRead, ArrayLen
 from tests.MockHandler import MockHandler
+from cbor.Decoder import Decoder
 
 
 def ignore_handler(v):
@@ -19,9 +20,9 @@ def test_run_array_length():
     data = CBORStream(BytesIO(bytes([0b10000011])))
     stack = ArrayInfo().run(data, ignore_handler)
     assert len(stack) == 2
-    assert type(stack[0]) == MajorType
-    assert type(stack[1]) == ArrayRead
-    assert stack[1] == ArrayRead(3)
+    assert type(stack[0]) == ArrayRead
+    assert stack[0] == ArrayRead(3)
+    assert type(stack[1]) == MajorType
 
 
 def test_run_array_length_multibyte():
@@ -35,9 +36,9 @@ def test_run_array_length_multibyte():
     assert stack[0] == ArrayLen(1)
     stack2 = stack[0].run(data, ignore_handler)
     assert len(stack2) == 2
-    assert type(stack2[0]) == MajorType
-    assert type(stack2[1]) == ArrayRead
-    assert stack2[1] == ArrayRead(1)
+    assert type(stack2[0]) == ArrayRead
+    assert stack2[0] == ArrayRead(1)
+    assert type(stack2[1]) == MajorType
 
     # Array length on 2 bytes.
     data = CBORStream(BytesIO(bytes([
@@ -49,9 +50,9 @@ def test_run_array_length_multibyte():
     assert stack[0] == ArrayLen(2)
     stack2 = stack[0].run(data, ignore_handler)
     assert len(stack2) == 2
-    assert type(stack2[0]) == MajorType
-    assert type(stack2[1]) == ArrayRead
-    assert stack2[1] == ArrayRead(1 << 8)
+    assert type(stack2[0]) == ArrayRead
+    assert stack2[0] == ArrayRead(1 << 8)
+    assert type(stack2[1]) == MajorType
 
     # Array length on 4 bytes.
     data = CBORStream(BytesIO(bytes([
@@ -63,9 +64,9 @@ def test_run_array_length_multibyte():
     assert stack[0] == ArrayLen(4)
     stack2 = stack[0].run(data, ignore_handler)
     assert len(stack2) == 2
-    assert type(stack2[0]) == MajorType
-    assert type(stack2[1]) == ArrayRead
-    assert stack2[1] == ArrayRead(1 << 24)
+    assert type(stack2[0]) == ArrayRead
+    assert stack2[0] == ArrayRead(1 << 24)
+    assert type(stack2[1]) == MajorType
 
     # Array length on 8 bytes.
     data = CBORStream(BytesIO(bytes([
@@ -77,9 +78,9 @@ def test_run_array_length_multibyte():
     assert stack[0] == ArrayLen(8)
     stack2 = stack[0].run(data, ignore_handler)
     assert len(stack2) == 2
-    assert type(stack2[0]) == MajorType
-    assert type(stack2[1]) == ArrayRead
-    assert stack2[1] == ArrayRead(1 << 56)
+    assert type(stack2[0]) == ArrayRead
+    assert stack2[0] == ArrayRead(1 << 56)
+    assert type(stack2[1]) == MajorType
 
 
 def test_run_array_read():
@@ -90,3 +91,26 @@ def test_run_array_read():
     stack = ArrayInfo().run(data, handler.handler)
     assert len(stack) == 0
     handler.assert_data('[]')
+
+
+def test_run_array_single_element():
+    handler = MockHandler()
+
+    # Empty array.
+    d = Decoder()
+    data = bytes([0b10000001, 0b10000000])
+    d.decode_array(data, handler.handler)
+    handler.assert_data('[[]]')
+
+
+def test_run_array_two_elements():
+    handler = MockHandler()
+
+    # Empty array.
+    d = Decoder()
+    data = bytes([
+        0b10000010,
+        0b10000000,
+        0b10000000])
+    d.decode_array(data, handler.handler)
+    handler.assert_data('[[],[]]')
diff --git a/tests/test_Tag.py b/tests/test_Tag.py
new file mode 100644
index 0000000..c57d23d
--- /dev/null
+++ b/tests/test_Tag.py
@@ -0,0 +1,83 @@
+from io import BytesIO
+from cbor.MajorType import MajorType
+from cbor.CBORStream import CBORStream
+from cbor.type.Tag import TagInfo, TagRead
+from tests.MockHandler import MockHandler
+
+
+def ignore_handler(v):
+    return
+
+
+def test_run_tag_probe():
+    data = CBORStream(BytesIO(bytes([0b11000001])))
+    assert type(MajorType().run(data, None)) == TagInfo
+
+
+def test_run_tag_length():
+    # Tag length lower than 24.
+    data = CBORStream(BytesIO(bytes([0b11000011])))
+    stack = TagInfo().run(data, ignore_handler)
+    assert len(stack) == 1
+    assert type(stack[0]) == MajorType
+
+
+def test_run_tag_length_multibyte():
+    # Tag length on 1 byte.
+    data = CBORStream(BytesIO(bytes([
+        0b11011000, 0b1
+    ])))
+    stack = TagInfo().run(data, ignore_handler)
+    assert len(stack) == 1
+    assert type(stack[0]) == TagRead
+    assert stack[0] == TagRead(1)
+    stack2 = stack[0].run(data, ignore_handler)
+    assert len(stack2) == 1
+    assert type(stack2[0]) == MajorType
+
+    # Tag length on 2 bytes.
+    data = CBORStream(BytesIO(bytes([
+        0b11011001, 0b1, 0b0
+    ])))
+    stack = TagInfo().run(data, ignore_handler)
+    assert len(stack) == 1
+    assert type(stack[0]) == TagRead
+    assert stack[0] == TagRead(2)
+    stack2 = stack[0].run(data, ignore_handler)
+    assert len(stack2) == 1
+    assert type(stack2[0]) == MajorType
+
+    # Tag length on 4 bytes.
+    data = CBORStream(BytesIO(bytes([
+        0b11011010, 0b1, 0b0, 0b0, 0b0
+    ])))
+    stack = TagInfo().run(data, ignore_handler)
+    assert len(stack) == 1
+    assert type(stack[0]) == TagRead
+    assert stack[0] == TagRead(4)
+    stack2 = stack[0].run(data, ignore_handler)
+    assert len(stack2) == 1
+    assert type(stack2[0]) == MajorType
+
+    # Tag length on 8 bytes.
+    data = CBORStream(BytesIO(bytes([
+        0b11011011, 0b1, 0b0, 0b0, 0b0, 0b0, 0b0, 0b0, 0b0
+    ])))
+    stack = TagInfo().run(data, ignore_handler)
+    assert len(stack) == 1
+    assert type(stack[0]) == TagRead
+    assert stack[0] == TagRead(8)
+    stack2 = stack[0].run(data, ignore_handler)
+    assert len(stack2) == 1
+    assert type(stack2[0]) == MajorType
+
+
+def test_run_tag_read():
+    handler = MockHandler()
+
+    # No output expected.
+    data = CBORStream(BytesIO(bytes([0b11000000])))
+    stack = TagInfo().run(data, handler.handler)
+    assert len(stack) == 1
+    assert type(stack[0]) == MajorType
+    handler.assert_data('')