diff --git a/Claude.MD b/Claude.MD index 7a670f1..7fd5704 100644 --- a/Claude.MD +++ b/Claude.MD @@ -112,8 +112,9 @@ PureBasic include for creating/reading MessagePack data. Wraps a given buffer `*Buffer` of a given size `InitialSize.i` into an internal structure. \ Returns `#Null` on errors. -* Procedure.b MsgPackFree(*MsgPackData.MsgPackData, FreeDataBuffer.b = #False) \ - Frees a given internal structure `*MsgPackData` and its associated buffer if `FreeDataBuffer` is `#True`. +* `MsgPackFree.b(*MsgPackData.MsgPackData, FreeDataBuffer.b)` \ + Frees a given internal structure `*MsgPackData` and its associated buffer if `FreeDataBuffer` is `#True`. \ + `FreeDataBuffer` is mandatory, use `#False` for buffers wrapped with `MsgPackWrapMsgBuffer` that the caller still owns. \ Returns `#True` if freed properly, `#False` otherwise. * `MsgPackFinalize.i(*MsgPackData.MsgPackData)` \ diff --git a/Includes/_Allocators.pbi b/Includes/_Allocators.pbi index a7a4148..178641e 100644 --- a/Includes/_Allocators.pbi +++ b/Includes/_Allocators.pbi @@ -95,7 +95,9 @@ Procedure.i MsgPackWrapMsgBuffer(*Buffer, BufferSize.i, GiveOwnership.b = #False ProcedureReturn *ReturnedValue EndProcedure -Procedure.b MsgPackFree(*MsgPackData.MsgPackData, FreeDataBuffer.b = #False) +; Frees a structure, and its buffer too if `FreeDataBuffer` is `#True`. +; `FreeDataBuffer` has no default on purpose: forgetting it either leaks the buffer or frees one that's still in use. +Procedure.b MsgPackFree(*MsgPackData.MsgPackData, FreeDataBuffer.b) If Not *MsgPackData ProcedureReturn #False EndIf diff --git a/Includes/_Binary.pbi b/Includes/_Binary.pbi index a67e456..d534662 100644 --- a/Includes/_Binary.pbi +++ b/Includes/_Binary.pbi @@ -57,7 +57,7 @@ XIncludeFile "./_Primitives.pbi" ; The caller is expected to write the extra header bytes and `DataSize` bytes of data, ; and move the offset afterward. ; Sets the structure's error code and returns it. -Procedure.MsgPack_ErrorCode _MsgPackWriteSizedFieldHeader(*MsgPackData.MsgPackData, FormatCode.MsgPack_FormatCode, SizeFieldByteLength.a, DataSize.q, ExtraHeaderSize.a = 0) +Procedure.MsgPack_ErrorCode _MsgPackWriteSizedFieldHeader(*MsgPackData.MsgPackData, FormatCode.MsgPack_FormatCode, SizeFieldByteLength.a, DataSize.q, ExtraHeaderSize.a = 0, *Data = #Null) Protected MaxDataSize.q Protected ErrorCode.MsgPack_ErrorCode @@ -93,6 +93,13 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteSizedFieldHeader(*MsgPackData.MsgPackDa ProcedureReturn #MsgPack_Error_BinaryDataSizeIsTooBig EndIf + ; Growing the buffer would free data taken from it before it's copied. + If _MsgPackOverlapsBuffer(*MsgPackData, *Data, DataSize) + DebuggerError("Cannot write data located in the destination's own buffer !") + *MsgPackData\LastError = #MsgPack_Error_InvalidValue + ProcedureReturn #MsgPack_Error_InvalidValue + EndIf + ErrorCode = _MsgPackReserveWrite(*MsgPackData, 1 + SizeFieldByteLength + ExtraHeaderSize + DataSize) If ErrorCode <> #MsgPack_Error_Success ProcedureReturn ErrorCode @@ -131,7 +138,7 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteSizedField(*MsgPackData.MsgPackData, Fo EndIf CompilerEndIf - ErrorCode = _MsgPackWriteSizedFieldHeader(*MsgPackData, FormatCode, SizeFieldByteLength, DataSize) + ErrorCode = _MsgPackWriteSizedFieldHeader(*MsgPackData, FormatCode, SizeFieldByteLength, DataSize, 0, *Data) If ErrorCode <> #MsgPack_Error_Success ProcedureReturn ErrorCode EndIf diff --git a/Includes/_Commons.pbi b/Includes/_Commons.pbi index 61c228b..b15fd70 100644 --- a/Includes/_Commons.pbi +++ b/Includes/_Commons.pbi @@ -44,6 +44,18 @@ CompilerIf Not Defined(MsgPack_DisableNullChecks, #PB_Constant) #MsgPack_DisableNullChecks = #False CompilerEndIf +; Maximum nesting depth of arrays and maps, a top-level array or map being at depth 1. +; Deeper data is rejected as invalid with `#MsgPack_Error_MaxDepthExceeded`. +; Bounds the time spent on hostile data, nested views re-check their content at each level. +; Each depth level costs 8 bytes of stack per skip, hence the upper bound. +CompilerIf Not Defined(MsgPack_MaxDepth, #PB_Constant) + #MsgPack_MaxDepth = 128 +CompilerEndIf + +CompilerIf #MsgPack_MaxDepth < 1 Or #MsgPack_MaxDepth > 4096 + CompilerError "#MsgPack_MaxDepth must be between 1 and 4096 !" +CompilerEndIf + ; ------------------------------------------------------------------------------ @@ -167,6 +179,7 @@ Enumeration _MsgPack_ErrorCodes ; 7x - Arrays & Maps #MsgPack_Error_TooManyEntries = 70 #MsgPack_Error_OddMapFieldCount = 71 + #MsgPack_Error_MaxDepthExceeded = 72 #MsgPack_Error_CannotGrowBufferDueToConfig = 100 @@ -256,6 +269,10 @@ Structure MsgPackData ; Number of fields left to iterate on, map keys and values included, only used by views. IteratorFieldsLeft.q + ; Number of arrays and maps containing the data, `0` for regular structures, `1` for views over a top-level one. + ; Counted against `#MsgPack_MaxDepth`. + IteratorDepth.i + ; LastError code LastError.MsgPack_ErrorCode @@ -284,6 +301,12 @@ Macro _MsgPackHasFlag(MsgPackWrapper, DataFlag) ((MsgPackWrapper\DataFlags & DataFlag) <> $0000) EndMacro +; Checks if the `Size` bytes at `Pointer` overlap the buffer of `MsgPackWrapper`. +; Writers refuse such data, growing the buffer would free it before it's copied. +Macro _MsgPackOverlapsBuffer(MsgPackWrapper, Pointer, Size) + (Pointer <> #Null And (Size) > 0 And Pointer < MsgPackWrapper\Buffer + MsgPackWrapper\BufferSize And Pointer + (Size) > MsgPackWrapper\Buffer) +EndMacro + ; Moves the offset past a field of `Size` bytes that was fully read, if the flags allow it. Macro _MsgPackMoveOffsetAfterRead(MsgPackWrapper, Size) If _MsgPackHasFlag(MsgPackWrapper, #MsgPack_DataFlags_MoveOffsetAfterRead) @@ -321,7 +344,9 @@ EndProcedure CompilerIf #True Procedure _MsgPackDebug(Message.s) Debug Message - OutputDebugString_(Message) + CompilerIf #PB_Compiler_OS = #PB_OS_Windows + OutputDebugString_(Message) + CompilerEndIf EndProcedure CompilerElse Macro _MsgPackDebug(Message) : : EndMacro diff --git a/Includes/_Extensions.pbi b/Includes/_Extensions.pbi index 4ad70de..6e0ae6c 100644 --- a/Includes/_Extensions.pbi +++ b/Includes/_Extensions.pbi @@ -84,6 +84,13 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteExtField(*MsgPackData.MsgPackData, Form ProcedureReturn #MsgPack_Error_InvalidValue EndIf + ; Growing the buffer would free data taken from it before it's copied. + If _MsgPackOverlapsBuffer(*MsgPackData, *Data, DataSize) + DebuggerError("Cannot write data located in the destination's own buffer !") + *MsgPackData\LastError = #MsgPack_Error_InvalidValue + ProcedureReturn #MsgPack_Error_InvalidValue + EndIf + ErrorCode = _MsgPackReserveWrite(*MsgPackData, 2 + DataSize) If ErrorCode <> #MsgPack_Error_Success ProcedureReturn ErrorCode @@ -92,7 +99,7 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteExtField(*MsgPackData.MsgPackData, Form PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, FormatCode) *MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 1 Else - ErrorCode = _MsgPackWriteSizedFieldHeader(*MsgPackData, FormatCode, SizeFieldByteLength, DataSize, 1) + ErrorCode = _MsgPackWriteSizedFieldHeader(*MsgPackData, FormatCode, SizeFieldByteLength, DataSize, 1, *Data) If ErrorCode <> #MsgPack_Error_Success ProcedureReturn ErrorCode EndIf diff --git a/Includes/_Iterators.pbi b/Includes/_Iterators.pbi index 911072d..06764be 100644 --- a/Includes/_Iterators.pbi +++ b/Includes/_Iterators.pbi @@ -258,6 +258,13 @@ Procedure.MsgPack_ErrorCode _MsgPackGetFieldLayout(*MsgPackData.MsgPackData, *He ChildCount = ChildCount * 2 EndIf + ; Each contained field takes at least 1 byte, bogus counts are rejected right away. + If ChildCount > *MsgPackData\BufferSize - *MsgPackData\BufferOffset - ValueStartOffset + Debug("The array or map has more entries than the buffer can hold !") + *MsgPackData\LastError = #MsgPack_Error_BufferTooSmall + ProcedureReturn #MsgPack_Error_BufferTooSmall + EndIf + *HeaderSize\i = ValueStartOffset *ChildCount\q = ChildCount @@ -278,8 +285,19 @@ Procedure.MsgPack_ErrorCode _MsgPackGetFieldLayout(*MsgPackData.MsgPackData, *He EndProcedure +; Checks if a format code byte is the one of an array or map, empty ones included. +Macro _MsgPackIsContainerFormatByte(Byte) + ((Byte & $E0) = $80 Or (Byte >= $DC And Byte <= $DF)) +EndMacro + +; Fields left to skip at each nesting level, the top level being `0`. +Structure _MsgPackSkipLevels + FieldsLeft.q[#MsgPack_MaxDepth + 1] +EndStructure + ; Moves the offset past the next `FieldCount` fields, the ones contained in arrays and maps included. ; Done iteratively so deeply nested arrays and maps can't overflow the stack. +; Arrays and maps nested deeper than `#MsgPack_MaxDepth`, counting the ones containing the structure, are rejected. ; The offset is left untouched on errors. ; Sets the structure's error code and returns it. Procedure.MsgPack_ErrorCode _MsgPackSkipFields(*MsgPackData.MsgPackData, FieldCount.q) @@ -287,6 +305,9 @@ Procedure.MsgPack_ErrorCode _MsgPackSkipFields(*MsgPackData.MsgPackData, FieldCo Protected HeaderSize.i Protected DataSize.q Protected ChildCount.q + Protected Level.i = 0 + Protected Levels._MsgPackSkipLevels + Protected FormatByte.a Protected ErrorCode.MsgPack_ErrorCode = #MsgPack_Error_Success CompilerIf Not #MsgPack_DisableNullChecks @@ -298,14 +319,43 @@ Procedure.MsgPack_ErrorCode _MsgPackSkipFields(*MsgPackData.MsgPackData, FieldCo StartOffset = *MsgPackData\BufferOffset + ; Each field takes at least 1 byte, which also keeps `FieldCount` far from overflowing below. + If FieldCount > *MsgPackData\BufferSize - *MsgPackData\BufferOffset And Not _MsgPackIsAtTheEnd(*MsgPackData) + Debug("End of buffer reached, " + Str(FieldCount) + " fields can't fit !") + *MsgPackData\LastError = #MsgPack_Error_BufferTooSmall + ProcedureReturn #MsgPack_Error_BufferTooSmall + EndIf + + ; `FieldCount` is the total left to skip, all levels included. + Levels\FieldsLeft[0] = FieldCount + While FieldCount > 0 + ; Leaving the arrays and maps whose content was fully skipped. + While Levels\FieldsLeft[Level] = 0 + Level = Level - 1 + Wend + ErrorCode = _MsgPackGetFieldLayout(*MsgPackData, @HeaderSize, @DataSize, @ChildCount) If ErrorCode <> #MsgPack_Error_Success Break EndIf + ; Arrays and maps are at depth `Level + 1` from the structure's content. + FormatByte = PeekA(*MsgPackData\Buffer + *MsgPackData\BufferOffset) + If _MsgPackIsContainerFormatByte(FormatByte) And *MsgPackData\IteratorDepth + Level + 1 > #MsgPack_MaxDepth + Debug("Arrays and maps are nested deeper than " + Str(#MsgPack_MaxDepth) + " levels !") + *MsgPackData\LastError = #MsgPack_Error_MaxDepthExceeded + ErrorCode = #MsgPack_Error_MaxDepthExceeded + Break + EndIf + *MsgPackData\BufferOffset = *MsgPackData\BufferOffset + HeaderSize + DataSize FieldCount = FieldCount - 1 + ChildCount + Levels\FieldsLeft[Level] = Levels\FieldsLeft[Level] - 1 + If ChildCount > 0 + Level = Level + 1 + Levels\FieldsLeft[Level] = ChildCount + EndIf ; Each field takes at least 1 byte, which rejects bogus array and map sizes early. If FieldCount > *MsgPackData\BufferSize - *MsgPackData\BufferOffset @@ -442,6 +492,13 @@ Procedure.i MsgPackExamine(*MsgPackData.MsgPackData, ExaminationOptions.MsgPack_ ProcedureReturn #Null EndIf + ; Unsafe views aren't checked beforehand, nesting them is limited here. + If *MsgPackData\IteratorDepth >= #MsgPack_MaxDepth + Debug("Arrays and maps are nested deeper than " + Str(#MsgPack_MaxDepth) + " levels !") + *MsgPackData\LastError = #MsgPack_Error_MaxDepthExceeded + ProcedureReturn #Null + EndIf + StartOffset = *MsgPackData\BufferOffset IsUnsafe = Bool(ExaminationOptions & #MsgPack_ExaminationOptions_Unsafe) @@ -471,6 +528,7 @@ Procedure.i MsgPackExamine(*MsgPackData.MsgPackData, ExaminationOptions.MsgPack_ *View\DataFlags = #_MsgPack_ViewDataFlags *View\IteratorEntryOffset = #MsgPack_InvalidSize *View\IteratorFieldsLeft = ChildCount + *View\IteratorDepth = *MsgPackData\IteratorDepth + 1 If DataType = #MsgPack_DataTypes_Map *View\DataFlags = *View\DataFlags | #MsgPack_DataFlags_CanProvideKeys @@ -532,6 +590,13 @@ Procedure.b MsgPackNextEntry(*Iterator.MsgPackData) ProcedureReturn #False EndIf + ; Unsafe views can announce more entries than their buffer holds. + If *Iterator\IteratorEntryOffset >= *Iterator\BufferSize + Debug("End of buffer reached, the remaining entries are missing !") + *Iterator\LastError = #MsgPack_Error_AtOrPastTheEndOfBuffer + ProcedureReturn #False + EndIf + ProcedureReturn #True EndProcedure @@ -575,6 +640,8 @@ Procedure.q _MsgPackCountFields(*MsgPackData.MsgPackData, *Buffer, BufferSize.q) ; Temporary wrapper, so the regular procedures can walk the data. Source\Buffer = *Buffer Source\BufferSize = BufferSize + ; The fields will be in the written array or map, so the result stays within `#MsgPack_MaxDepth`. + Source\IteratorDepth = 1 While Not _MsgPackIsAtTheEnd(Source) If _MsgPackSkipFields(@Source, 1) <> #MsgPack_Error_Success @@ -607,7 +674,7 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteContainer(*MsgPackData.MsgPackData, IsM CompilerEndIf ; The content's address would be invalidated when growing. - If *Buffer And *Buffer >= *MsgPackData\Buffer And *Buffer < *MsgPackData\Buffer + *MsgPackData\BufferSize + If _MsgPackOverlapsBuffer(*MsgPackData, *Buffer, BufferSize) DebuggerError("Cannot write content located in the destination's own buffer !") *MsgPackData\LastError = #MsgPack_Error_InvalidValue ProcedureReturn #MsgPack_Error_InvalidValue diff --git a/UnitTests/60_Iterators/TestDepthLimit.pb b/UnitTests/60_Iterators/TestDepthLimit.pb new file mode 100644 index 0000000..48490ab --- /dev/null +++ b/UnitTests/60_Iterators/TestDepthLimit.pb @@ -0,0 +1,153 @@ +EnableExplicit + +; Must be defined before the include, it replaces the default of 128. +#MsgPack_MaxDepth = 3 + +XIncludeFile "../_UnitTesting.pbi" +XIncludeFile "../../Includes/MessagePack.pbi" +XIncludeFile "../_TestBuffers.pbi" + + +; Setup +Define *MsgPack.MsgPackData +Define *Content.MsgPackData +Define *Output.MsgPackData +Define *View1.MsgPackData +Define *View2.MsgPackData +Define *View3.MsgPackData +Define *View4.MsgPackData + + +Procedure TestDepth(Hex.s, ExpectedSize.q, TestName.s) + Protected *MsgPack.MsgPackData = WrapHex(Hex) + + AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), ExpectedSize, TestName + " (Size)") + If ExpectedSize = #MsgPack_InvalidSize + AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_MaxDepthExceeded, TestName + " (Error)") + AssertEquals(MsgPackSkip(*MsgPack), #MsgPack_Error_MaxDepthExceeded, TestName + " (Skip)") + AssertEquals(*MsgPack\BufferOffset, 0, TestName + " (Skip leaves the offset)") + AssertIsTrue(Bool(MsgPackExamine(*MsgPack) = #Null), TestName + " (Examine)") + AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_MaxDepthExceeded, TestName + " (Examine error)") + AssertEquals(*MsgPack\BufferOffset, 0, TestName + " (Examine leaves the offset)") + Else + AssertEquals(MsgPackSkip(*MsgPack), #MsgPack_Error_Success, TestName + " (Skip)") + AssertEquals(*MsgPack\BufferOffset, ExpectedSize, TestName + " (Skip offset)") + EndIf + + FreeWrappedHex(*MsgPack) +EndProcedure + + +; Boundaries, a top-level array or map being at depth 1 +TestDepth("919191C0", 4, "Arrays nested 3 levels deep") +TestDepth("91919191C0", #MsgPack_InvalidSize, "Arrays nested 4 levels deep") +TestDepth("919190", 3, "Empty array at depth 3") +TestDepth("91919190", #MsgPack_InvalidSize, "Empty array at depth 4") +TestDepth("91919180", #MsgPack_InvalidSize, "Empty map at depth 4") +TestDepth("DC0001DC0001DC0001C0", 10, "Array16 nested 3 levels deep") +TestDepth("DD00000001DD00000001DD00000001DD00000001C0", #MsgPack_InvalidSize, "Array32 nested 4 levels deep") + +; Maps, as keys and as values +TestDepth("818180C0C0", 5, "Maps nested 3 levels deep as keys") +TestDepth("81818180C0C0C0", #MsgPack_InvalidSize, "Maps nested 4 levels deep as keys") +TestDepth("81C081C081C0C0", 7, "Maps nested 3 levels deep as values") +TestDepth("81C081C081C081C0C0", #MsgPack_InvalidSize, "Maps nested 4 levels deep as values") + +; The depth goes back down between siblings +TestDepth("9291C091C0", 5, "Siblings at depth 2") +TestDepth("929191C09191C0", 7, "Siblings reaching depth 3") +TestDepth("82C09191C0C0C0", 7, "Map value reaching depth 3") +TestDepth("92C0919191C0", #MsgPack_InvalidSize, "Second sibling reaching depth 4") +TestDepth("939191C0C0919191C0", #MsgPack_InvalidSize, "Third sibling reaching depth 4") + +; Skipping several fields, each one starting back at depth 1 +*MsgPack = WrapHex("919191C0919191C0") +AssertEquals(MsgPackSkip(*MsgPack, 2), #MsgPack_Error_Success, "Skipped 2 fields 3 levels deep") +AssertEquals(*MsgPack\BufferOffset, 8, "Offset after the 2 fields") +FreeWrappedHex(*MsgPack) + + +; Checked views: each level is reachable, the content was checked by the top-level one +*MsgPack = WrapHex("919191C0") +*View1 = MsgPackExamine(*MsgPack) +AssertIsTrue(Bool(*View1 <> #Null), "Examined depth 1") +AssertIsTrue(MsgPackNextEntry(*View1), "Depth 1 entry") +*View2 = MsgPackExamine(*View1) +AssertIsTrue(Bool(*View2 <> #Null), "Examined depth 2") +AssertIsTrue(MsgPackNextEntry(*View2), "Depth 2 entry") +*View3 = MsgPackExamine(*View2) +AssertIsTrue(Bool(*View3 <> #Null), "Examined depth 3") +AssertIsTrue(MsgPackNextEntry(*View3), "Depth 3 entry") +AssertEquals(MsgPackReadNull(*View3), #MsgPack_Error_Success, "Read the value at depth 3") +AssertIsFalse(MsgPackNextEntry(*View3), "End of depth 3") +MsgPackFinish(*View3) +AssertIsFalse(MsgPackNextEntry(*View2), "End of depth 2") +MsgPackFinish(*View2) +AssertIsFalse(MsgPackNextEntry(*View1), "End of depth 1") +MsgPackFinish(*View1) +FreeWrappedHex(*MsgPack) + + +; Unsafe views aren't checked beforehand, their own nesting is limited +*MsgPack = WrapHex("91919191C0") +*View1 = MsgPackExamine(*MsgPack, #MsgPack_ExaminationOptions_Unsafe) +AssertIsTrue(Bool(*View1 <> #Null), "Unsafe examined depth 1") +AssertIsTrue(MsgPackNextEntry(*View1), "Unsafe depth 1 entry") +*View2 = MsgPackExamine(*View1, #MsgPack_ExaminationOptions_Unsafe) +AssertIsTrue(Bool(*View2 <> #Null), "Unsafe examined depth 2") +AssertIsTrue(MsgPackNextEntry(*View2), "Unsafe depth 2 entry") +*View3 = MsgPackExamine(*View2, #MsgPack_ExaminationOptions_Unsafe) +AssertIsTrue(Bool(*View3 <> #Null), "Unsafe examined depth 3") +AssertIsTrue(MsgPackNextEntry(*View3), "Unsafe depth 3 entry") +*View4 = MsgPackExamine(*View3, #MsgPack_ExaminationOptions_Unsafe) +AssertIsTrue(Bool(*View4 = #Null), "Unsafe depth 4 is rejected") +AssertEquals(MsgPackLastError(*View3), #MsgPack_Error_MaxDepthExceeded, "Unsafe depth 4 error is set") +*View4 = MsgPackExamine(*View3) +AssertIsTrue(Bool(*View4 = #Null), "Checked depth 4 is rejected too") +AssertEquals(MsgPackLastError(*View3), #MsgPack_Error_MaxDepthExceeded, "Checked depth 4 error is set") +AssertIsFalse(MsgPackNextEntry(*View3), "The depth 4 array can't be skipped") +AssertEquals(MsgPackLastError(*View3), #MsgPack_Error_MaxDepthExceeded, "Skip error is set") +MsgPackFinish(*View3) +AssertIsFalse(MsgPackNextEntry(*View2), "Depth 2 can't skip its entry either") +MsgPackFinish(*View2) +MsgPackFinish(*View1) +FreeWrappedHex(*MsgPack) + + +; Writers can't produce data deeper than the limit +*Output = MsgPackCreate(16) + +*Content = WrapHex("9191C0") +AssertEquals(MsgPackWriteArray(*Output, *Content\Buffer, *Content\BufferSize), #MsgPack_Error_Success, "Array of content 2 levels deep") +AssertEquals(*Output\BufferOffset, 4, "Array written") +FreeWrappedHex(*Content) + +*Content = WrapHex("919191C0") +AssertEquals(MsgPackWriteArray(*Output, *Content\Buffer, *Content\BufferSize), #MsgPack_Error_MaxDepthExceeded, "Array of content 3 levels deep") +AssertEquals(MsgPackLastError(*Output), #MsgPack_Error_MaxDepthExceeded, "Writer error is set") +AssertEquals(MsgPackWriteMap(*Output, *Content\Buffer, *Content\BufferSize), #MsgPack_Error_MaxDepthExceeded, "Map of content 3 levels deep") +AssertEquals(*Output\BufferOffset, 4, "Nothing was written") +FreeWrappedHex(*Content) + +*Content = WrapHex("C09191C0") +AssertEquals(MsgPackWriteMap(*Output, *Content\Buffer, *Content\BufferSize), #MsgPack_Error_Success, "Map with a value 2 levels deep") +AssertEquals(*Output\BufferOffset, 9, "Map written") +FreeWrappedHex(*Content) + +; The written data is at the limit, and can be read back +*Output\BufferOffset = 0 +AssertEqualsQ(MsgPackGetFieldFullSize(*Output), 4, "Written array is readable") +*Output\BufferOffset = 4 +AssertEqualsQ(MsgPackGetFieldFullSize(*Output), 5, "Written map is readable") + +MsgPackFree(*Output, #True) + + + +If FailedUnitTests + End 1 +EndIf + +Debug "Done !" + +End 0 diff --git a/UnitTests/60_Iterators/TestDepthLimitMax.pb b/UnitTests/60_Iterators/TestDepthLimitMax.pb new file mode 100644 index 0000000..48d4bc5 --- /dev/null +++ b/UnitTests/60_Iterators/TestDepthLimitMax.pb @@ -0,0 +1,77 @@ +EnableExplicit + +; Biggest supported value, deep nesting must still be handled iteratively. +#MsgPack_MaxDepth = 4096 + +XIncludeFile "../_UnitTesting.pbi" +XIncludeFile "../../Includes/MessagePack.pbi" +XIncludeFile "../_TestBuffers.pbi" + + +; Setup +Define *MsgPack.MsgPackData +Define *Buffer +Define i.i +Define Depth.i +Define *View.MsgPackData +Dim Views.i(4096) + + +Procedure.i WrapNested(Depth.i, Size.i) + Protected *Buffer = AllocateMemory(Size) + FillMemory(*Buffer, Depth, $91) + FillMemory(*Buffer + Depth, Size - Depth, $C0) + ProcedureReturn MsgPackWrapMsgBuffer(*Buffer, Size) +EndProcedure + + +; At the limit +*MsgPack = WrapNested(4096, 4097) +AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), 4097, "Arrays nested 4096 levels deep") +AssertEquals(MsgPackSkip(*MsgPack), #MsgPack_Error_Success, "Skipped arrays nested 4096 levels deep") + +; Every level examined, with checked views +*MsgPack\BufferOffset = 0 +Views(0) = *MsgPack +For i = 1 To 4096 + Views(i) = MsgPackExamine(Views(i - 1)) + If Not Views(i) + Break + EndIf + If Not MsgPackNextEntry(Views(i)) + MsgPackFinish(Views(i)) + Break + EndIf +Next +Depth = i - 1 +AssertEquals(Depth, 4096, "Examined every level") +If Depth = 4096 + AssertEquals(MsgPackReadNull(Views(4096)), #MsgPack_Error_Success, "Read the value at depth 4096") +EndIf +For i = Depth To 1 Step -1 + *View = Views(i) + MsgPackFinish(*View) +Next +FreeWrappedHex(*MsgPack) + +; Past the limit +*MsgPack = WrapNested(4097, 4098) +AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), #MsgPack_InvalidSize, "Arrays nested 4097 levels deep") +AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_MaxDepthExceeded, "Max depth error is set") +FreeWrappedHex(*MsgPack) + +; Far past it, rejected without walking the rest +*MsgPack = WrapNested(1000000, 1000001) +AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), #MsgPack_InvalidSize, "Arrays nested 1M levels deep") +AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_MaxDepthExceeded, "Max depth error is set for 1M levels") +FreeWrappedHex(*MsgPack) + + + +If FailedUnitTests + End 1 +EndIf + +Debug "Done !" + +End 0 diff --git a/UnitTests/60_Iterators/TestIterators.pb b/UnitTests/60_Iterators/TestIterators.pb index c0baa07..2cd493d 100644 --- a/UnitTests/60_Iterators/TestIterators.pb +++ b/UnitTests/60_Iterators/TestIterators.pb @@ -75,13 +75,18 @@ AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), #MsgPack_InvalidSize, "Truncate AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Truncated UInt16 error is set") FreeWrappedHex(*MsgPack) -; Deep nesting doesn't overflow the stack +; Deep nesting, up to `#MsgPack_MaxDepth` (128 by default), see "TestDepthLimit*.pb" for the rest Hex = "" -For i = 1 To 10000 +For i = 1 To 128 Hex = Hex + "91" Next *MsgPack = WrapHex(Hex + "01") -AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), 10001, "Deeply nested arrays") +AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), 129, "Arrays nested 128 levels deep") +FreeWrappedHex(*MsgPack) + +*MsgPack = WrapHex("91" + Hex + "01") +AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), #MsgPack_InvalidSize, "Arrays nested 129 levels deep are rejected") +AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_MaxDepthExceeded, "Max depth error is set") FreeWrappedHex(*MsgPack)