@@ -674,7 +674,13 @@ module DynamoToStruct {
674674 // = specification/dynamodb-encryption-client/ddb-attribute-serialization.md#list-entry-value
675675 // # A List MAY hold any DynamoDB Attribute Value data type,
676676 // # and MAY hold values of different types.
677- function method {:opaque} CollectList (
677+
678+ // Can't be {:tailrecursion} because it calls AttrToBytes which might again call CollectList
679+ // However, we really need this to loop and not recurse.
680+ // This verifies without the `by method`, but Dafny is too broken to let it verify by method
681+ // for example, a call to CollectList somehow does not satisfy the decreases clause
682+ // hence the {:verify false}
683+ function {:opaque} {:verify false } {:axiom} CollectList (
678684 listToSerialize : ListAttributeValue ,
679685 depth : uint32 ,
680686 serialized : seq <uint8 > := []
@@ -687,10 +693,18 @@ module DynamoToStruct {
687693 if |listToSerialize| == 0 then
688694 Success (serialized)
689695 else
690- // Can't do the `pos` optimization, because I can't appease the `decreases`
691696 var val :- AttrToBytes (listToSerialize[0], true, depth+1);
692697 CollectList (listToSerialize[1..], depth, serialized + val)
693698 }
699+ by method {
700+ var result := serialized;
701+ for i := 0 to |listToSerialize|
702+ {
703+ var val :- AttrToBytes (listToSerialize[i], true, depth+1);
704+ result := result + val;
705+ }
706+ return Success (result);
707+ }
694708
695709 function method SerializeMapItem (key : string , value : seq <uint8 >) : (ret : Result< seq < uint8> , string > )
696710
@@ -892,31 +906,23 @@ module DynamoToStruct {
892906 DeserializeNumberSet (serialized[len..], remainingCount-1, origSerializedSize, AttrValueAndLength(nattr, resultSet.len + len + LENGTH_LEN32))
893907 }
894908
895- // Bytes to List
896- // Can't be {:tailrecursion} because it calls BytesToAttr which might again call DeserializeList
897- function method {:vcs_split_on_every_assert} {:opaque} DeserializeList (
909+ function method {:vcs_split_on_every_assert} DeserializeListEntry (
898910 serialized : seq <uint8 >,
899911 pos : uint32 ,
900- ghost orig_pos : uint32 ,
901- remainingCount : uint32 ,
902912 depth : uint32 ,
903913 resultList : AttrValueAndLength
904914 )
905- : (ret : Result< AttrValueAndLength, string > )
915+ : (ret : Result< ( AttrValueAndLength, uint32) , string > )
906916 requires |serialized| < UINT32_MAX as int
907917 requires pos as int <= |serialized|
908- requires orig_pos <= pos
909918 requires depth <= MAX_STRUCTURE_DEPTH
910919 requires resultList. val. L?
911- ensures ret. Success? ==> ret. value. val. L?
912- requires pos == Add32 (orig_pos, resultList.len)
913- ensures ret. Success? ==> ret. value. len <= |serialized| as uint32 - orig_pos
914- decreases |serialized| as uint32 - pos
920+ ensures ret. Success? ==> ret. value. 0. val. L?
921+ ensures ret. Success? ==> pos < ret. value. 1 <= |serialized| as uint32
922+ decreases |serialized| as uint32 - pos, 0
915923 {
916924 var serialized_size := |serialized| as uint32;
917- if remainingCount == 0 then
918- Success (resultList)
919- else if serialized_size- pos < PREFIX_LEN32 then
925+ if serialized_size- pos < PREFIX_LEN32 then
920926 Failure ("Out of bytes reading Type of List element")
921927 else
922928 var TerminalTypeId := serialized[pos.. pos+ 2];
@@ -927,9 +933,57 @@ module DynamoToStruct {
927933 else
928934 assert serialized_size == |serialized| as uint32;
929935 var nval :- BytesToAttr (serialized, TerminalTypeId, Some(len), depth+ 1, new_pos);
936+ var new_pos := new_pos + nval. len;
930937 var nattr := AttributeValue. L (resultList.val.L + [nval.val]);
931- var nResultList := AttrValueAndLength (nattr, Add32_3(resultList.len, len, PREFIX_LEN32));
932- DeserializeList (serialized, new_pos+len, orig_pos, remainingCount-1, depth, nResultList)
938+ var nResultList := AttrValueAndLength (nattr, Add32(resultList.len, new_pos-pos));
939+ Success ((nResultList, new_pos))
940+ }
941+
942+ // Bytes to List
943+ // Can't be {:tailrecursion} because it calls BytesToAttr which might again call DeserializeList
944+ // However, we really need this to loop and not recurse.
945+ // This verifies without the `by method`, but Dafny is too broken to let it verify by method
946+ // for example, a call to DeserializeListEntry somehow does not satisfy the decreases clause
947+ // hence the {:verify false}
948+ function {:vcs_split_on_every_assert} {:opaque} {:verify false } DeserializeList (
949+ serialized : seq <uint8 >,
950+ pos : uint32 ,
951+ ghost orig_pos : uint32 ,
952+ remainingCount : uint32 ,
953+ depth : uint32 ,
954+ resultList : AttrValueAndLength
955+ )
956+ : (ret : Result< AttrValueAndLength, string > )
957+ requires |serialized| < UINT32_MAX as int
958+ requires pos as int <= |serialized|
959+ requires orig_pos <= pos
960+ requires depth <= MAX_STRUCTURE_DEPTH
961+ requires resultList. val. L?
962+ ensures ret. Success? ==> ret. value. val. L?
963+ requires pos == Add32 (orig_pos, resultList.len)
964+ ensures ret. Success? ==> ret. value. len <= |serialized| as uint32 - orig_pos
965+ decreases |serialized| as uint32 - pos, 1
966+ {
967+ if remainingCount == 0 then
968+ Success (resultList)
969+ else
970+ var (newResultList, npos) :- DeserializeListEntry (serialized, pos, depth, resultList);
971+ DeserializeList (serialized, npos, orig_pos, remainingCount - 1, depth, newResultList)
972+ }
973+ by method {
974+ var npos : uint32 := pos;
975+ var newResultList := resultList;
976+ for i := 0 to remainingCount
977+ invariant serialized == old (serialized)
978+ invariant newResultList. val. L?
979+ invariant npos as int <= |serialized|
980+ invariant npos == Add32 (orig_pos, newResultList.len)
981+ {
982+ var test :- DeserializeListEntry (serialized, npos, depth, newResultList);
983+ newResultList := test. 0;
984+ npos := test. 1;
985+ }
986+ ret := Success (newResultList);
933987 }
934988
935989 function method {:vcs_split_on_every_assert} DeserializeMapEntry (
@@ -990,6 +1044,7 @@ module DynamoToStruct {
9901044 // Can't be {:tailrecursion} because it calls BytesToAttr which might again call DeserializeMap
9911045 // However, we really need this to loop and not recurse.
9921046 // This verifies without the `by method`, but Dafny is too broken to let it verify by method
1047+ // for example, a call to DeserializeMapEntry somehow does not satisfy the decreases clause
9931048 // hence the {:verify false}
9941049 function {:vcs_split_on_every_assert} {:opaque} {:verify false } DeserializeMap (
9951050 serialized : seq <uint8 >,
0 commit comments