Implemented Iterators, Arrays and Maps

Ready for test integration, Used stuff setup a while back
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-11-01 23:10:05 +01:00
parent f61f016030
commit 2ba7f061ee
14 changed files with 1279 additions and 182 deletions
+89
View File
@@ -10,7 +10,10 @@ PureBasic include for creating/reading MessagePack data.
* ???
* The internal offset is auto-incremented on all complete reads and writes.
* Does not apply to `MsgPackGetFormatCode`.
* Can be disabled with the internal `#MsgPack_DataFlags_MoveOffsetAfterRead/Write` flags, honored by every read and write.
* The internal buffer is automatically resized as needed.
* Invalid or truncated data never raises a `DebuggerError`, only programming errors do. (e.g.: `#Null` pointers)
* Views from `MsgPackExamine` are read-only, writes return `#MsgPack_Error_BufferIsReadOnly`.
### Type aliases/macros
@@ -86,7 +89,10 @@ PureBasic include for creating/reading MessagePack data.
* `#MsgPack_Error_OutputBufferTooSmall`
* `#MsgPack_Error_UnsupportedStringEncoding`
* `#MsgPack_Error_InvalidStringSize`
* `#MsgPack_Error_TooManyEntries`
* `#MsgPack_Error_OddMapFieldCount`
* `#MsgPack_Error_CannotGrowBufferDueToConfig`
* `#MsgPack_Error_BufferIsReadOnly`
* `.MsgPack_DataFlag` \
Internal flags, do not touch.
@@ -137,6 +143,14 @@ PureBasic include for creating/reading MessagePack data.
Attempts to write `#False` at the current internal offset. \
Returns `#MsgPack_Error_Success` on success, something else otherwise.
* `MsgPackWriteBoolean.MsgPack_ErrorCode(*MsgPackData.MsgPackData, Boolean.b)`
Writes `#True` or `#False` depending on `Boolean`. \
Returns `#MsgPack_Error_Success` on success, something else otherwise.
* `MsgPackReadNull.MsgPack_ErrorCode(*MsgPackData.MsgPackData)`
Attempts to read a nil at the current internal offset. \
Returns `#MsgPack_Error_Success` on success, something else otherwise, in which case the offset isn't moved.
### Integers & Floats
* `MsgPackWriteInteger.MsgPack_ErrorCode(*MsgPackData.MsgPackData, Value.q)` \
@@ -226,6 +240,81 @@ Size errors reuse the `#MsgPack_Error_BinaryDataSize*` codes.
Same as `MsgPackCopyBinary`, for the data of ext fields.
### Navigation
Arrays and maps are handled iteratively, so deep nesting can't overflow the stack.
* `MsgPackGetFieldFullSize.q(*MsgPackData.MsgPackData)` \
Returns the full size in bytes of the field at the current internal offset, without moving it. \
Arrays and maps include all of their contained fields, recursively. \
Returns `#MsgPack_InvalidSize` on errors and sets the internal error code. \
See: `MsgPackLastError`
* `MsgPackGetEntryCount.q(*MsgPackData.MsgPackData)` \
Returns the number of entries in the array or map at the current internal offset, without moving it. \
A map entry is a key and a value, and counts as one. \
Returns `#MsgPack_InvalidSize` on errors and sets the internal error code.
* `MsgPackSkip.MsgPack_ErrorCode(*MsgPackData.MsgPackData, FieldCount.q = 1)` \
Moves the internal offset past the next `FieldCount` fields, the ones contained in arrays and maps included. \
Map keys and values count as separate fields. \
Returns `#MsgPack_Error_Success` on success, something else otherwise, in which case the offset isn't moved.
### Arrays & Maps
* `MsgPackExamine.i(*MsgPackData.MsgPackData, ExaminationOptions.MsgPack_ExaminationOption = 0)` \
Returns a view over the array or map at the current internal offset, to iterate on with `MsgPackNextEntry`. \
The view is a regular `MsgPackData` limited to the array/map's content, so all the regular read functions work on it, and can't read past it. \
It reads straight from `*MsgPackData`'s buffer, which must not be grown or freed while the view is used. \
The whole array/map is checked beforehand, and the offset of `*MsgPackData` is moved past it. \
Returns `#Null` on errors and sets the internal error code. \
See: `MsgPackLastError`
* `#MsgPack_ExaminationOptions_Unsafe` - **Not recommended !** \
Skips the content check, which is also what finds the array/map's end. \
The view can read past the array/map until `MsgPackNextEntry` reaches its end, invalid content is only
detected while iterating, and the offset of `*MsgPackData` isn't moved, use `MsgPackSkip` afterward.
* `MsgPackNextEntry.b(*Iterator.MsgPackData)` \
Moves the view's offset to its next entry, the first one on the first call. \
Whatever wasn't read from the current entry is skipped, nested arrays/maps included. \
Map entries are read key first, then value. \
Returns `#True` if an entry is available, `#False` at the end or on errors, with the internal error code set accordingly.
* `MsgPackFinish(*Iterator.MsgPackData)` \
Frees a view from `MsgPackExamine`, without touching the underlying buffer.
```purebasic
Define *Iterator.MsgPackData = MsgPackExamine(*MsgPack)
If *Iterator
While MsgPackNextEntry(*Iterator)
Key.s = MsgPackReadString(*Iterator)
If MsgPackFormatCodeToDataType(MsgPackGetFormatCode(*Iterator)) = #MsgPack_DataTypes_Map
*Nested = MsgPackExamine(*Iterator) ; Or leave it, it'll be skipped
; ...
MsgPackFinish(*Nested)
EndIf
Wend
MsgPackFinish(*Iterator)
EndIf
```
* `MsgPackWriteArray.MsgPack_ErrorCode(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)` \
Writes an array whose entries are the fields in the `BufferSize` bytes of MessagePack data at `*Buffer`, using the smallest array format code that fits. \
The data is fully checked and its fields are counted, nested arrays/maps counting as one. \
`*Buffer` can come from `MsgPackFinalize`, with `MemorySize()` as its size, but not from `*MsgPackData`'s own buffer. \
Returns `#MsgPack_Error_Success` on success, something else otherwise, with nothing written.
* `MsgPackWriteMap.MsgPack_ErrorCode(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)` \
Same as `MsgPackWriteArray`, for maps. \
The data must hold an even number of fields, alternating keys and values, or `#MsgPack_Error_OddMapFieldCount` is returned.
* `MsgPackWriteArrayFrom/MapFrom.MsgPack_ErrorCode(*MsgPackData.MsgPackData, *Source.MsgPackData)` \
Same as `MsgPackWriteArray/Map`, with the data written so far in `*Source`, which is left untouched.
* `MsgPackWriteFixArray/Array16/Array32/FixMap/Map16/Map32.MsgPack_ErrorCode(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)` \
Same as `MsgPackWriteArray/Map`, but forces the given format code. \
Returns `#MsgPack_Error_TooManyEntries` if the entries don't fit in it.
## Remarks
* Never touch the `MsgPackData` structure unless you **absolutely** have to.
+1 -2
View File
@@ -151,8 +151,7 @@ EndProcedure
; TODO: Add callback in the structure, maybe ?
; FIXME: Use a .q for the size !!!!
Procedure.MsgPack_ErrorCode MsgPackGrow(*MsgPackData.MsgPackData, DesiredSizeIncrease.i)
Procedure.MsgPack_ErrorCode MsgPackGrow(*MsgPackData.MsgPackData, DesiredSizeIncrease.q)
Protected NewSize.q
Protected *NewBuffer
+5 -4
View File
@@ -140,6 +140,7 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteSizedField(*MsgPackData.MsgPackData, Fo
CopyMemory(*Data, *MsgPackData\Buffer + *MsgPackData\BufferOffset, DataSize)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + DataSize
EndIf
_MsgPackRewindOffsetAfterWrite(*MsgPackData, 1 + SizeFieldByteLength + DataSize)
ProcedureReturn #MsgPack_Error_Success
EndProcedure
@@ -273,8 +274,8 @@ Procedure.q _MsgPackReadSizedFieldData(*MsgPackData.MsgPackData, ExpectedDataTyp
CopyMemory(*MsgPackData\Buffer + *MsgPackData\BufferOffset + HeaderSize, *OutputBuffer, DataSize)
EndIf
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + HeaderSize + DataSize
_MsgPackMoveOffsetAfterRead(*MsgPackData, HeaderSize + DataSize)
ProcedureReturn DataSize
EndProcedure
@@ -304,8 +305,8 @@ Procedure.i _MsgPackCopySizedFieldData(*MsgPackData.MsgPackData, ExpectedDataTyp
CopyMemory(*MsgPackData\Buffer + *MsgPackData\BufferOffset + HeaderSize, *ReturnedValue, DataSize)
EndIf
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + HeaderSize + DataSize
_MsgPackMoveOffsetAfterRead(*MsgPackData, HeaderSize + DataSize)
ProcedureReturn *ReturnedValue
EndProcedure
+44 -10
View File
@@ -29,9 +29,10 @@ CompilerIf #PB_Compiler_IsMainFile
EnableExplicit
CompilerEndIf
; FIXME: Implement something to not load the include twice in other programs
#_MsgPack_EndiannessIncludePath = "./PB-Endianness/Includes/Endianness.pbi"
XIncludeFile #_MsgPack_EndiannessIncludePath
; Skipped if "Endianness.pbi" was already included from another path, by the program or another include.
CompilerIf Not Defined(_NibblePoker_Endianness_WasIncluded, #PB_Constant) And Not Defined(EndianSwapQ, #PB_Procedure)
XIncludeFile "./PB-Endianness/Includes/Endianness.pbi"
CompilerEndIf
@@ -163,12 +164,18 @@ Enumeration _MsgPack_ErrorCodes
#MsgPack_Error_UnsupportedStringEncoding = 60
#MsgPack_Error_InvalidStringSize = 61
; 7x - Arrays & Maps
#MsgPack_Error_TooManyEntries = 70
#MsgPack_Error_OddMapFieldCount = 71
#MsgPack_Error_CannotGrowBufferDueToConfig = 100
#MsgPack_Error_BufferIsReadOnly = 101
EndEnumeration
EnumerationBinary _MsgPack_DataFlags
; FIXME: Not implemented
; Prevents any write in a given `MsgPackData`, set on views from `MsgPackExamine()`.
; Writes return `#MsgPack_Error_BufferIsReadOnly`.
#MsgPack_DataFlags_IsReadOnly = %0000000000000001
; Prevents a `MsgPackData` structure from auto-growing its buffer when needed.
@@ -186,22 +193,29 @@ EnumerationBinary _MsgPack_DataFlags
; writing a value using the standard procedures.
#MsgPack_DataFlags_MoveOffsetAfterWrite = %0000000000010000
; FIXME: Not implemented
; Indicates a given `MsgPackData` is made to be iterated on.
; Indicates a given `MsgPackData` is a view made to be iterated on with `MsgPackNextEntry()`.
; And it implies it represents a Map or Array.
#MsgPack_DataFlags_Iterator = %0000000000100000
; Set on map views, whose entries are made of a key and a value.
#MsgPack_DataFlags_CanProvideKeys = %0000000001000000
; Set on both map and array views.
#MsgPack_DataFlags_CanProvideVals = %0000000010000000
EndEnumeration
; Flags set on every new `MsgPackData`, so all reads and writes move the offset.
#_MsgPack_DefaultDataFlags = #MsgPack_DataFlags_MoveOffsetAfterRead | #MsgPack_DataFlags_MoveOffsetAfterWrite
; Flags set on every view from `MsgPackExamine()`, which never own nor grow their buffer.
EnumerationBinary _MsgPack_ExaminationOptions
; If set, we don't seek the end of the examined map/array.
#MsgPack_ExaminationOptions_NoEndSeeking = %0000000000000001
; Not recommended !
; Skips the check of the examined array or map's content, which also finds its end.
; The view can then read past the array or map until `MsgPackNextEntry()` reaches its end,
; invalid content is only detected while iterating, and the examined offset isn't moved.
#MsgPack_ExaminationOptions_Unsafe = %0000000000000001
EndEnumeration
#_MsgPack_ViewDataFlags =#_MsgPack_DefaultDataFlags | #MsgPack_DataFlags_IsReadOnly | #MsgPack_DataFlags_CannotGrow | #MsgPack_DataFlags_NoBufferFree | #MsgPack_DataFlags_Iterator | #MsgPack_DataFlags_CanProvideVals
; ------------------------------------------------------------------------------
@@ -235,8 +249,13 @@ Structure MsgPackData
; If set to `1` or lower, we'll ignore this mechanism.
BufferGrowthIncrement.i
; IteratorSizeLeft, or snoething like this.
; Offset of the iterator's current entry, only used by views from `MsgPackExamine()`.
; Set to `#MsgPack_InvalidSize` until the first `MsgPackNextEntry()` call.
IteratorEntryOffset.i
; Number of fields left to iterate on, map keys and values included, only used by views.
IteratorFieldsLeft.q
; LastError code
LastError.MsgPack_ErrorCode
@@ -265,6 +284,21 @@ Macro _MsgPackHasFlag(MsgPackWrapper, DataFlag)
((MsgPackWrapper\DataFlags & DataFlag) <> $0000)
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)
MsgPackWrapper\BufferOffset = MsgPackWrapper\BufferOffset + (Size)
EndIf
EndMacro
; Moves the offset back before a field of `Size` bytes that was fully written, if the flags don't allow it to move.
; Writes always move the offset as they go, since most of them are done in multiple steps.
Macro _MsgPackRewindOffsetAfterWrite(MsgPackWrapper, Size)
If Not _MsgPackHasFlag(MsgPackWrapper, #MsgPack_DataFlags_MoveOffsetAfterWrite)
MsgPackWrapper\BufferOffset = MsgPackWrapper\BufferOffset - (Size)
EndIf
EndMacro
; ------------------------------------------------------------------------------
+2
View File
@@ -105,6 +105,8 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteExtField(*MsgPackData.MsgPackData, Form
CopyMemory(*Data, *MsgPackData\Buffer + *MsgPackData\BufferOffset, DataSize)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + DataSize
EndIf
; Format code, ext type, and size field for non-fixext ones.
_MsgPackRewindOffsetAfterWrite(*MsgPackData, 2 + SizeFieldByteLength + DataSize)
ProcedureReturn #MsgPack_Error_Success
EndProcedure
+558 -127
View File
@@ -26,6 +26,7 @@ CompilerEndIf
XIncludeFile "./_Commons.pbi"
XIncludeFile "./_Utils.pbi"
XIncludeFile "./_Allocators.pbi"
XIncludeFile "./_Binary.pbi"
@@ -52,7 +53,7 @@ Macro _MsgPackGetFieldDynamicSize_Within(MsgPackData, Mask) : (PeekA(MsgPackData
; Remark: Doesn't apply to Int/UInt variants since those have their own function for extracting the value.
Procedure.q _MsgPackGetFieldDynamicSize_1After(*MsgPackData.MsgPackData)
If Not _MsgPackHasSpaceLeft(*MsgPackData, 1 + 1)
DebuggerWarning("We have less than 1 byte to read the field's dynamic size !")
Debug("We have less than 1 byte to read the field's dynamic size !")
ProcedureReturn #MsgPack_InvalidSize
EndIf
@@ -64,7 +65,7 @@ EndProcedure
; Remark: Doesn't apply to Int/UInt variants since those have their own function for extracting the value.
Procedure.q _MsgPackGetFieldDynamicSize_2After(*MsgPackData.MsgPackData)
If Not _MsgPackHasSpaceLeft(*MsgPackData, 1 + 2)
DebuggerWarning("We have less than 2 bytes to read the field's dynamic size !")
Debug("We have less than 2 bytes to read the field's dynamic size !")
ProcedureReturn #MsgPack_InvalidSize
EndIf
@@ -76,7 +77,7 @@ EndProcedure
; Remark: Doesn't apply to Int/UInt variants since those have their own function for extracting the value.
Procedure.q _MsgPackGetFieldDynamicSize_4After(*MsgPackData.MsgPackData)
If Not _MsgPackHasSpaceLeft(*MsgPackData, 1 + 4)
DebuggerWarning("We have less than 4 bytes to read the field's dynamic size !")
Debug("We have less than 4 bytes to read the field's dynamic size !")
ProcedureReturn #MsgPack_InvalidSize
EndIf
@@ -84,7 +85,9 @@ Procedure.q _MsgPackGetFieldDynamicSize_4After(*MsgPackData.MsgPackData)
EndProcedure
; Returns a given format code's spacing info based on itself and the data in the given MsgPack buffer.
; ???
; `*VariableSizeQ` receives the size of str/bin/ext data, or the number of entries in arrays/maps.
; `*ValueStartOffsetB` receives the offset of the value from the format code, ext type excluded.
; Both receive `#MsgPack_InvalidSize` and `#False` is returned for unsupported format codes.
; Remark: Doesn't apply to FixInt/Int/UInt variants since those have their own function for extracting the value.
Procedure _MsgPackGetFieldSpacingInfo(*MsgPackData.MsgPackData, ExaminedDataType.MsgPack_FormatCode, *VariableSizeQ = #Null, *ValueStartOffsetB = #Null)
CompilerIf Not #MsgPack_DisableNullChecks
@@ -100,23 +103,12 @@ Procedure _MsgPackGetFieldSpacingInfo(*MsgPackData.MsgPackData, ExaminedDataType
; The "within" sizes are read from the format code, which must be in the buffer.
If _MsgPackIsAtTheEnd(*MsgPackData)
DebuggerWarning("End of buffer reached, cannot read the field's dynamic size !")
Debug("End of buffer reached, cannot read the field's dynamic size !")
ExaminedDataType = #MsgPack_FormatCode_Invalid
EndIf
Select ExaminedDataType
Case #MsgPack_FormatCode_FixIntPositive:
If *VariableSizeQ <> #Null
PokeQ(*VariableSizeQ, _MsgPackGetFieldDynamicSize_Within(*MsgPackData, %01111111))
EndIf
If *ValueStartOffsetB <> #Null
; FIXME: Is this right ?!?
PokeB(*ValueStartOffsetB, 1)
EndIf
ProcedureReturn #True
Case #MsgPack_FormatCode_FixStr,
#MsgPack_FormatCode_FixIntNegative:
Case #MsgPack_FormatCode_FixStr:
If *VariableSizeQ <> #Null
PokeQ(*VariableSizeQ, _MsgPackGetFieldDynamicSize_Within(*MsgPackData, %00011111))
EndIf
@@ -187,89 +179,213 @@ Procedure _MsgPackGetFieldSpacingInfo(*MsgPackData.MsgPackData, ExaminedDataType
EndProcedure
;-> > Public
;-> Field layout
; NOTE: Not sure if making this public is useful :/
Procedure.q MsgPackGetFieldDynamicSize(*MsgPackData.MsgPackData, ExaminedDataType.MsgPack_FormatCode)
Protected ReturnedValue.q = 0
_MsgPackGetFieldSpacingInfo(*MsgPackData, ExaminedDataType, @ReturnedValue, #Null)
ProcedureReturn ReturnedValue
;-> > Private
; Reads the layout of the field at the current offset, without moving it.
; On success:
; * `*HeaderSize` receives the length of its format code, size field and ext type.
; * `*DataSize` receives the length of the data or value following its header.
; * `*ChildCount` receives the number of fields contained in an array or map, keys included.
; The header and data are guaranteed to be within the buffer, but contained fields aren't checked.
; Sets the structure's error code and returns it.
Procedure.MsgPack_ErrorCode _MsgPackGetFieldLayout(*MsgPackData.MsgPackData, *HeaderSize.Integer, *DataSize.Quad, *ChildCount.Quad)
Protected FormatCode.MsgPack_FormatCode
Protected DataType.MsgPack_DataType
Protected ChildCount.q
Protected ValueStartOffset.b
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn #MsgPack_Error_NullPointerGiven
EndIf
CompilerEndIf
If _MsgPackIsAtTheEnd(*MsgPackData)
Debug("End of buffer reached, cannot check data format code !")
*MsgPackData\LastError = #MsgPack_Error_AtOrPastTheEndOfBuffer
ProcedureReturn #MsgPack_Error_AtOrPastTheEndOfBuffer
EndIf
FormatCode = MsgPackGetFormatCode(*MsgPackData)
DataType = MsgPackFormatCodeToDataType(FormatCode)
*HeaderSize\i = 1
*DataSize\q = 0
*ChildCount\q = 0
Select DataType
Case #MsgPack_DataTypes_Null,
#MsgPack_DataTypes_Boolean,
#MsgPack_DataTypes_Integer,
#MsgPack_DataTypes_Float:
; Fixints, nil and bools only have their format code.
Select FormatCode
Case #MsgPack_FormatCode_UInt8,
#MsgPack_FormatCode_Int8:
*DataSize\q = 1
Case #MsgPack_FormatCode_UInt16,
#MsgPack_FormatCode_Int16:
*DataSize\q = 2
Case #MsgPack_FormatCode_UInt32,
#MsgPack_FormatCode_Int32,
#MsgPack_FormatCode_Float32:
*DataSize\q = 4
Case #MsgPack_FormatCode_UInt64,
#MsgPack_FormatCode_Int64,
#MsgPack_FormatCode_Float64:
*DataSize\q = 8
EndSelect
Case #MsgPack_DataTypes_String,
#MsgPack_DataTypes_Binary,
#MsgPack_DataTypes_Extension:
; Also checks that the whole field is within the buffer.
ProcedureReturn _MsgPackReadSizedFieldHeader(*MsgPackData, DataType, *HeaderSize, *DataSize)
Case #MsgPack_DataTypes_Array,
#MsgPack_DataTypes_Map:
_MsgPackGetFieldSpacingInfo(*MsgPackData, FormatCode, @ChildCount, @ValueStartOffset)
If ChildCount = #MsgPack_InvalidSize
Debug("End of buffer reached, cannot read the field's size !")
*MsgPackData\LastError = #MsgPack_Error_BufferTooSmall
ProcedureReturn #MsgPack_Error_BufferTooSmall
EndIf
If DataType = #MsgPack_DataTypes_Map
ChildCount = ChildCount * 2
EndIf
*HeaderSize\i = ValueStartOffset
*ChildCount\q = ChildCount
Default:
Debug("Invalid format code ! (" + RSet(Hex(PeekA(*MsgPackData\Buffer + *MsgPackData\BufferOffset)), 2, "0") + ")")
*MsgPackData\LastError = #MsgPack_Error_InvalidFormatCode
ProcedureReturn #MsgPack_Error_InvalidFormatCode
EndSelect
If Not _MsgPackHasSpaceLeft(*MsgPackData, *HeaderSize\i + *DataSize\q)
Debug("End of buffer reached, cannot read out of bounds !")
*MsgPackData\LastError = #MsgPack_Error_BufferTooSmall
ProcedureReturn #MsgPack_Error_BufferTooSmall
EndIf
*MsgPackData\LastError = #MsgPack_Error_Success
ProcedureReturn #MsgPack_Error_Success
EndProcedure
; 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.
; The offset is left untouched on errors.
; Sets the structure's error code and returns it.
Procedure.MsgPack_ErrorCode _MsgPackSkipFields(*MsgPackData.MsgPackData, FieldCount.q)
Protected StartOffset.i
Protected HeaderSize.i
Protected DataSize.q
Protected ChildCount.q
Protected ErrorCode.MsgPack_ErrorCode = #MsgPack_Error_Success
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn #MsgPack_Error_NullPointerGiven
EndIf
CompilerEndIf
StartOffset = *MsgPackData\BufferOffset
While FieldCount > 0
ErrorCode = _MsgPackGetFieldLayout(*MsgPackData, @HeaderSize, @DataSize, @ChildCount)
If ErrorCode <> #MsgPack_Error_Success
Break
EndIf
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + HeaderSize + DataSize
FieldCount = FieldCount - 1 + ChildCount
; Each field takes at least 1 byte, which rejects bogus array and map sizes early.
If FieldCount > *MsgPackData\BufferSize - *MsgPackData\BufferOffset
Debug("End of buffer reached, " + Str(FieldCount) + " fields are left to skip !")
*MsgPackData\LastError = #MsgPack_Error_BufferTooSmall
ErrorCode = #MsgPack_Error_BufferTooSmall
Break
EndIf
Wend
If ErrorCode <> #MsgPack_Error_Success
*MsgPackData\BufferOffset = StartOffset
ProcedureReturn ErrorCode
EndIf
*MsgPackData\LastError = #MsgPack_Error_Success
ProcedureReturn #MsgPack_Error_Success
EndProcedure
;-> Size getters
; Returns the full size in bytes of the field at the current offset, without moving it.
; Arrays and maps include all of their contained fields, recursively.
; OnError: Returns `#MsgPack_InvalidSize` and sets the `MsgPackData\LastError` field.
Procedure.q MsgPackGetFieldFullSize(*MsgPackData.MsgPackData)
Protected IsIterable = #False
Protected HasVariableSize = #False
Protected FieldVariableSize.q = #MsgPack_InvalidSize
Protected FieldValueOffset.b = #MsgPack_InvalidSize
Protected FullFieldSize.q = 0
Protected StartOffset.i
Protected FullSize.q
Protected ExaminedFormatCode.MsgPack_FormatCode
; Safety checks
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn #MsgPack_Error_NullPointerGiven
ProcedureReturn #MsgPack_InvalidSize
EndIf
CompilerEndIf
; Preparing stuff
*MsgPackData\LastError = #MsgPack_Error_Success
ExaminedFormatCode = MsgPackGetFormatCode(*MsgPackData)
; Determining iterability for later
If (ExaminedFormatCode >= #MsgPack_FormatCode_Array16 And ExaminedFormatCode <= #MsgPack_FormatCode_Map32) Or
(ExaminedFormatCode = #MsgPack_FormatCode_FixArray Or ExaminedFormatCode = #MsgPack_FormatCode_FixMap)
IsIterable = #True
EndIf
; We ignore the returned boolean since we maw not care about this result later down the line.
_MsgPackGetFieldSpacingInfo(*MsgPackData, ExaminedFormatCode, @FieldVariableSize, @FieldValueOffset)
If IsIterable
; Iterables, we'll have to go recursive
; They're also all using variable sizes.
DebuggerError("Getting an iterable field's full size isn't implemented yet !")
ElseIf FieldVariableSize = -1
; Simple fixed-size field, we'll have to determine the size
Else
; Variable size field
EndIf
; Step 3
ProcedureReturn FullFieldSize
StartOffset = *MsgPackData\BufferOffset
If _MsgPackSkipFields(*MsgPackData, 1) <> #MsgPack_Error_Success
ProcedureReturn #MsgPack_InvalidSize
EndIf
FullSize = *MsgPackData\BufferOffset - StartOffset
*MsgPackData\BufferOffset = StartOffset
ProcedureReturn FullSize
EndProcedure
;-> General
; Returns the number of entries in the array or map at the current offset, without moving it.
; Map entries are made of a key and a value, and count as one.
; OnError: Returns `#MsgPack_InvalidSize` and sets the `MsgPackData\LastError` field.
Procedure.q MsgPackGetEntryCount(*MsgPackData.MsgPackData)
Protected HeaderSize.i
Protected DataSize.q
Protected ChildCount.q
; TODO: Use the internal flag to detect if we should check the remaining count.
; MsgPackNext(MaximalDepth)
If _MsgPackGetFieldLayout(*MsgPackData, @HeaderSize, @DataSize, @ChildCount) <> #MsgPack_Error_Success
ProcedureReturn #MsgPack_InvalidSize
EndIf
Select MsgPackFormatCodeToDataType(MsgPackGetFormatCode(*MsgPackData))
Case #MsgPack_DataTypes_Array:
ProcedureReturn ChildCount
Case #MsgPack_DataTypes_Map:
ProcedureReturn ChildCount / 2
EndSelect
Debug("Unexpected format code ! (" + RSet(Hex(PeekA(*MsgPackData\Buffer + *MsgPackData\BufferOffset)), 2, "0") + ")")
*MsgPackData\LastError = #MsgPack_Error_InvalidFormatCode
ProcedureReturn #MsgPack_InvalidSize
EndProcedure
;-> Dictionary
;-> Navigation
; MsgPackExamineDictionary
Macro MsgPackFinishDictionary(MsgPackData) : MsgPackFree(MsgPackData, #False) : EndMacro
;-> Arrays
Procedure.i MsgPackExamineArray(*MsgPackData.MsgPackData, ExaminationOptions.MsgPack_ExaminationOption = 0)
Protected ExaminedFormatCode.MsgPack_FormatCode
Protected ExaminedDataType.MsgPack_FormatCode
Protected ExaminedArraySize.q
Protected *ExaminedData.MsgPackData = #Null
; Moves the offset past the next `FieldCount` fields, the ones contained in arrays and maps included.
; Map keys and values count as separate fields.
; Returns `#MsgPack_Error_Success` on success, something else otherwise, in which case the offset isn't moved.
Procedure.MsgPack_ErrorCode MsgPackSkip(*MsgPackData.MsgPackData, FieldCount.q = 1)
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData
DebuggerError("A #Null MsgPackData pointer was passed !")
@@ -277,54 +393,369 @@ Procedure.i MsgPackExamineArray(*MsgPackData.MsgPackData, ExaminationOptions.Msg
EndIf
CompilerEndIf
*MsgPackData\LastError = #MsgPack_Error_Success
If FieldCount < 0
DebuggerWarning("Cannot skip a negative amount of fields !")
*MsgPackData\LastError = #MsgPack_Error_InvalidValue
ProcedureReturn #MsgPack_Error_InvalidValue
EndIf
ExaminedFormatCode = MsgPackGetFormatCode(*MsgPackData)
ExaminedDataType = MsgPackGetFormatCode(ExaminedFormatCode)
If ExaminedDataType <> #MsgPack_DataTypes_Array
DebuggerWarning("Attempted to iterate on format code 0x" +
RSet(Hex(ExaminedFormatCode, #PB_Ascii), 2, "0") +
" as if it was an array !")
*MsgPackData\LastError = -1
ProcedureReturn #Null
EndIf
Select ExaminedFormatCode
Case #MsgPack_FormatCode_FixArray:
ExaminedArraySize = ExaminedFormatCode & $000000000000000F
Case #MsgPack_FormatCode_Array16:
If Not _MsgPackHasSpaceLeft(*MsgPackData, *MsgPackData\BufferSize + 3)
DebuggerWarning("We have less than 3 bytes to read the Array16's size !")
; FIXME: Use the proper one
*MsgPackData\LastError = -1
ProcedureReturn #Null
EndIf
ExaminedArraySize = PeekU(*MsgPackData\Buffer + 1)
Case #MsgPack_FormatCode_Array32:
If Not _MsgPackHasSpaceLeft(*MsgPackData, *MsgPackData\BufferSize + 5)
DebuggerWarning("We have less than 5 bytes to read the Array32's size !")
; FIXME: Use the proper one
*MsgPackData\LastError = -1
ProcedureReturn #Null
EndIf
; FIXME: WILL CAUSE ISSUES WITH UNSIGNED SHIT !!!
ExaminedArraySize = PeekI(*MsgPackData\Buffer + 1)
Default:
; ???
EndSelect
; ???
; Not implemented yet !
ProcedureReturn #Null
ProcedureReturn _MsgPackSkipFields(*MsgPackData, FieldCount)
EndProcedure
Macro MsgPackFinishArray(MsgPackData) : MsgPackFree(MsgPackData, #False) : EndMacro
;-> Iterators
; Prepares a view over the array or map at the current offset, to iterate on it with `MsgPackNextEntry()`.
; The view reads straight from the given structure's buffer, which must not be grown or freed while it is used.
; Its size is limited to the array or map's content, so reads can't go past it.
; The whole array or map is checked beforehand, and the offset is moved past it.
; `#MsgPack_ExaminationOptions_Unsafe` skips that check, which isn't recommended, see its definition.
; Returns: A view that must be freed with `MsgPackFinish()`.
; OnError: Returns `#Null` and sets the `MsgPackData\LastError` field.
Procedure.i MsgPackExamine(*MsgPackData.MsgPackData, ExaminationOptions.MsgPack_ExaminationOption = 0)
Protected DataType.MsgPack_DataType
Protected HeaderSize.i
Protected DataSize.q
Protected ChildCount.q
Protected StartOffset.i
Protected EndOffset.i
Protected IsUnsafe.b
Protected *View.MsgPackData
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn #Null
EndIf
CompilerEndIf
If _MsgPackGetFieldLayout(*MsgPackData, @HeaderSize, @DataSize, @ChildCount) <> #MsgPack_Error_Success
ProcedureReturn #Null
EndIf
DataType = MsgPackFormatCodeToDataType(MsgPackGetFormatCode(*MsgPackData))
If DataType <> #MsgPack_DataTypes_Array And DataType <> #MsgPack_DataTypes_Map
Debug("Attempted to examine format code 0x" +
RSet(Hex(PeekA(*MsgPackData\Buffer + *MsgPackData\BufferOffset)), 2, "0") +
" as if it was an array or map !")
*MsgPackData\LastError = #MsgPack_Error_InvalidFormatCode
ProcedureReturn #Null
EndIf
StartOffset = *MsgPackData\BufferOffset
IsUnsafe = Bool(ExaminationOptions & #MsgPack_ExaminationOptions_Unsafe)
If IsUnsafe
; The end is unknown, the view is only limited by `IteratorFieldsLeft` until it is reached.
EndOffset = *MsgPackData\BufferSize
Else
; Seeking the end of the array or map, which also checks all of its content.
If _MsgPackSkipFields(*MsgPackData, 1) <> #MsgPack_Error_Success
ProcedureReturn #Null
EndIf
EndOffset = *MsgPackData\BufferOffset
EndIf
*View = AllocateStructure(MsgPackData)
If Not *View
Debug("Failed to allocate memory for the view !")
*MsgPackData\BufferOffset = StartOffset
*MsgPackData\LastError = #MsgPack_Error_FailedToAllocateBuffer
ProcedureReturn #Null
EndIf
*View\Buffer = *MsgPackData\Buffer + StartOffset + HeaderSize
*View\BufferSize = EndOffset - StartOffset - HeaderSize
*View\BufferOffset = 0
*View\BufferGrowthIncrement = 1
*View\DataFlags = #_MsgPack_ViewDataFlags
*View\IteratorEntryOffset = #MsgPack_InvalidSize
*View\IteratorFieldsLeft = ChildCount
If DataType = #MsgPack_DataTypes_Map
*View\DataFlags = *View\DataFlags | #MsgPack_DataFlags_CanProvideKeys
EndIf
If IsUnsafe Or Not _MsgPackHasFlag(*MsgPackData, #MsgPack_DataFlags_MoveOffsetAfterRead)
*MsgPackData\BufferOffset = StartOffset
EndIf
*MsgPackData\LastError = #MsgPack_Error_Success
ProcedureReturn *View
EndProcedure
; Moves a view's offset to its next entry, skipping whatever wasn't read from the current one.
; The entry can then be read with the regular procedures, key first for maps.
; Nested arrays and maps can be examined or left as-is, they'll be skipped.
; Returns: `#True` if an entry is available, `#False` once the end of the view is reached.
; OnError: Returns `#False` and sets the `MsgPackData\LastError` field.
Procedure.b MsgPackNextEntry(*Iterator.MsgPackData)
Protected FieldsPerEntry.q = 1
CompilerIf Not #MsgPack_DisableNullChecks
If Not *Iterator
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn #False
EndIf
CompilerEndIf
If Not _MsgPackHasFlag(*Iterator, #MsgPack_DataFlags_Iterator)
DebuggerError("The given MsgPackData isn't a view from `MsgPackExamine()` !")
*Iterator\LastError = #MsgPack_Error_InvalidValue
ProcedureReturn #False
EndIf
If *Iterator\IteratorEntryOffset = #MsgPack_InvalidSize
; First call, the first entry is at the very start of the view.
*Iterator\IteratorEntryOffset = 0
ElseIf *Iterator\IteratorFieldsLeft > 0
; Skipping from the entry's start, regardless of what was read from it.
If _MsgPackHasFlag(*Iterator, #MsgPack_DataFlags_CanProvideKeys)
FieldsPerEntry = 2
EndIf
*Iterator\BufferOffset = *Iterator\IteratorEntryOffset
If _MsgPackSkipFields(*Iterator, FieldsPerEntry) <> #MsgPack_Error_Success
ProcedureReturn #False
EndIf
*Iterator\IteratorEntryOffset = *Iterator\BufferOffset
*Iterator\IteratorFieldsLeft = *Iterator\IteratorFieldsLeft - FieldsPerEntry
EndIf
*Iterator\BufferOffset = *Iterator\IteratorEntryOffset
*Iterator\LastError = #MsgPack_Error_Success
If *Iterator\IteratorFieldsLeft <= 0
; Unsafe views only find their end here, so reads can't go past it afterward.
*Iterator\BufferSize = *Iterator\IteratorEntryOffset
ProcedureReturn #False
EndIf
ProcedureReturn #True
EndProcedure
Macro MsgPackFinish(Iterator) : MsgPackFree(Iterator, #False) : EndMacro
;-> Setters
;-> > Private
; Used in place of a size field's length to pick the smallest one that fits.
#_MsgPack_SmallestSizeField = $FF
; Counts the fields in the `BufferSize` bytes at `*Buffer`, the ones contained in arrays and maps excluded.
; The whole data is checked, and must end on a field's boundary.
; Returns: The number of fields.
; OnError: Returns `#MsgPack_InvalidSize` and sets the `MsgPackData\LastError` field.
Procedure.q _MsgPackCountFields(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)
Protected Source.MsgPackData
Protected FieldCount.q = 0
If BufferSize < 0
DebuggerWarning("Received negative size !")
*MsgPackData\LastError = #MsgPack_Error_BinaryDataSizeIsNegative
ProcedureReturn #MsgPack_InvalidSize
ElseIf BufferSize > #_MsgPack_MaxIntegerValue
DebuggerWarning("Requested size can't be addressed: " + Str(BufferSize))
*MsgPackData\LastError = #MsgPack_Error_BinaryDataSizeIsTooBig
ProcedureReturn #MsgPack_InvalidSize
EndIf
CompilerIf Not #MsgPack_DisableNullChecks
If BufferSize > 0 And Not *Buffer
DebuggerError("A #Null data buffer was passed !")
*MsgPackData\LastError = #MsgPack_Error_NullPointerGiven
ProcedureReturn #MsgPack_InvalidSize
EndIf
CompilerEndIf
; Temporary wrapper, so the regular procedures can walk the data.
Source\Buffer = *Buffer
Source\BufferSize = BufferSize
While Not _MsgPackIsAtTheEnd(Source)
If _MsgPackSkipFields(@Source, 1) <> #MsgPack_Error_Success
*MsgPackData\LastError = Source\LastError
ProcedureReturn #MsgPack_InvalidSize
EndIf
FieldCount = FieldCount + 1
Wend
ProcedureReturn FieldCount
EndProcedure
; Writes an array or map whose content is a copy of the `BufferSize` bytes of MessagePack data at `*Buffer`.
; Map content must hold an even number of fields, alternating keys and values.
; `SizeFieldByteLength` follows the "sized field" helpers' convention, `0` being FixArray and FixMap,
; or is `#_MsgPack_SmallestSizeField` to use the smallest format code that fits.
; Sets the structure's error code and returns it.
Procedure.MsgPack_ErrorCode _MsgPackWriteContainer(*MsgPackData.MsgPackData, IsMap.b, SizeFieldByteLength.a, *Buffer, BufferSize.q)
Protected EntryCount.q
Protected MaxEntryCount.q
Protected FormatCode.MsgPack_FormatCode
Protected ErrorCode.MsgPack_ErrorCode
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn #MsgPack_Error_NullPointerGiven
EndIf
CompilerEndIf
; The content's address would be invalidated when growing.
If *Buffer And *Buffer >= *MsgPackData\Buffer And *Buffer < *MsgPackData\Buffer + *MsgPackData\BufferSize
DebuggerError("Cannot write content located in the destination's own buffer !")
*MsgPackData\LastError = #MsgPack_Error_InvalidValue
ProcedureReturn #MsgPack_Error_InvalidValue
EndIf
EntryCount = _MsgPackCountFields(*MsgPackData, *Buffer, BufferSize)
If EntryCount = #MsgPack_InvalidSize
ProcedureReturn *MsgPackData\LastError
EndIf
If IsMap
If EntryCount % 2 <> 0
Debug("Map content has an odd number of fields (" + Str(EntryCount) + "), keys and values must be paired !")
*MsgPackData\LastError = #MsgPack_Error_OddMapFieldCount
ProcedureReturn #MsgPack_Error_OddMapFieldCount
EndIf
EntryCount = EntryCount / 2
EndIf
If SizeFieldByteLength = #_MsgPack_SmallestSizeField
If EntryCount <= 15
SizeFieldByteLength = 0
ElseIf EntryCount <= 65535
SizeFieldByteLength = 2
Else
SizeFieldByteLength = 4
EndIf
EndIf
Select SizeFieldByteLength
Case 0 :
MaxEntryCount = 15
FormatCode = #MsgPack_FormatCode_FixArray
If IsMap : FormatCode = #MsgPack_FormatCode_FixMap : EndIf
Case 2 :
MaxEntryCount = 65535
FormatCode = #MsgPack_FormatCode_Array16
If IsMap : FormatCode = #MsgPack_FormatCode_Map16 : EndIf
Case 4 :
MaxEntryCount = 4294967295
FormatCode = #MsgPack_FormatCode_Array32
If IsMap : FormatCode = #MsgPack_FormatCode_Map32 : EndIf
Default
DebuggerError("Invalid size field length: " + Str(SizeFieldByteLength))
*MsgPackData\LastError = #MsgPack_Error_InvalidValue
ProcedureReturn #MsgPack_Error_InvalidValue
EndSelect
If EntryCount > MaxEntryCount
DebuggerWarning("Content has more than " + Str(MaxEntryCount) + " entries !")
*MsgPackData\LastError = #MsgPack_Error_TooManyEntries
ProcedureReturn #MsgPack_Error_TooManyEntries
EndIf
ErrorCode = _MsgPackReserveWrite(*MsgPackData, 1 + SizeFieldByteLength + BufferSize)
If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode
EndIf
; Each field takes at least 1 byte, so the header won't reserve more than what was reserved above.
ErrorCode = _MsgPackWriteSizedFieldHeader(*MsgPackData, FormatCode, SizeFieldByteLength, EntryCount)
If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode
EndIf
If BufferSize > 0
CopyMemory(*Buffer, *MsgPackData\Buffer + *MsgPackData\BufferOffset, BufferSize)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + BufferSize
EndIf
_MsgPackRewindOffsetAfterWrite(*MsgPackData, 1 + SizeFieldByteLength + BufferSize)
ProcedureReturn #MsgPack_Error_Success
EndProcedure
;-> > Public
; Writes an array whose entries are the fields in the `BufferSize` bytes of MessagePack data at `*Buffer`,
; using the smallest array format code that fits.
; `*Buffer` can come from `MsgPackFinalize()`, with `MemorySize()` as its size.
; Returns `#MsgPack_Error_Success` on success, something else otherwise.
Procedure.MsgPack_ErrorCode MsgPackWriteArray(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)
ProcedureReturn _MsgPackWriteContainer(*MsgPackData, #False, #_MsgPack_SmallestSizeField, *Buffer, BufferSize)
EndProcedure
; Same as `MsgPackWriteArray()`, for maps.
; The data must hold an even number of fields, alternating keys and values.
; Returns `#MsgPack_Error_Success` on success, something else otherwise.
Procedure.MsgPack_ErrorCode MsgPackWriteMap(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)
ProcedureReturn _MsgPackWriteContainer(*MsgPackData, #True, #_MsgPack_SmallestSizeField, *Buffer, BufferSize)
EndProcedure
; Same as `MsgPackWriteArray()`, with the data written so far in `*Source`.
; `*Source` is left untouched and must be freed by the caller.
; Returns `#MsgPack_Error_Success` on success, something else otherwise.
Procedure.MsgPack_ErrorCode MsgPackWriteArrayFrom(*MsgPackData.MsgPackData, *Source.MsgPackData)
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData Or Not *Source
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn #MsgPack_Error_NullPointerGiven
EndIf
CompilerEndIf
ProcedureReturn MsgPackWriteArray(*MsgPackData, *Source\Buffer, *Source\BufferOffset)
EndProcedure
; Same as `MsgPackWriteMap()`, with the data written so far in `*Source`.
; `*Source` is left untouched and must be freed by the caller.
; Returns `#MsgPack_Error_Success` on success, something else otherwise.
Procedure.MsgPack_ErrorCode MsgPackWriteMapFrom(*MsgPackData.MsgPackData, *Source.MsgPackData)
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData Or Not *Source
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn #MsgPack_Error_NullPointerGiven
EndIf
CompilerEndIf
ProcedureReturn MsgPackWriteMap(*MsgPackData, *Source\Buffer, *Source\BufferOffset)
EndProcedure
;-> Macros
; Same as `MsgPackWriteArray()` and `MsgPackWriteMap()`, but force the given format code.
Macro MsgPackWriteFixArray(MsgPackData, Buffer, BufferSize)
_MsgPackWriteContainer(MsgPackData, #False, 0, Buffer, BufferSize)
EndMacro
Macro MsgPackWriteArray16(MsgPackData, Buffer, BufferSize)
_MsgPackWriteContainer(MsgPackData, #False, 2, Buffer, BufferSize)
EndMacro
Macro MsgPackWriteArray32(MsgPackData, Buffer, BufferSize)
_MsgPackWriteContainer(MsgPackData, #False, 4, Buffer, BufferSize)
EndMacro
Macro MsgPackWriteFixMap(MsgPackData, Buffer, BufferSize)
_MsgPackWriteContainer(MsgPackData, #True, 0, Buffer, BufferSize)
EndMacro
Macro MsgPackWriteMap16(MsgPackData, Buffer, BufferSize)
_MsgPackWriteContainer(MsgPackData, #True, 2, Buffer, BufferSize)
EndMacro
Macro MsgPackWriteMap32(MsgPackData, Buffer, BufferSize)
_MsgPackWriteContainer(MsgPackData, #True, 4, Buffer, BufferSize)
EndMacro
+40 -26
View File
@@ -25,6 +25,7 @@ CompilerEndIf
XIncludeFile "./_Commons.pbi"
XIncludeFile "./_Allocators.pbi"
XIncludeFile "./_Primitives.pbi"
@@ -33,20 +34,25 @@ XIncludeFile "./_Allocators.pbi"
;-> Getters
;Procedure.i MsgPackReadNull(*MsgPackData.MsgPackData)
; CompilerIf Not #MsgPack_DisableNullChecks
; If Not *MsgPackData
; DebuggerError("A #Null MsgPackData pointer was passed !")
; ProcedureReturn #Null
; EndIf
; CompilerEndIf
;EndProcedure
; Reads a nil from the given data.
; Returns `#MsgPack_Error_Success` on success, something else otherwise.
Procedure.MsgPack_ErrorCode MsgPackReadNull(*MsgPackData.MsgPackData)
Protected ErrorCode.MsgPack_ErrorCode
ErrorCode = _MsgPackExpectRead(*MsgPackData, #MsgPack_FormatCode_Null, 1)
If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode
EndIf
_MsgPackMoveOffsetAfterRead(*MsgPackData, 1)
ProcedureReturn #MsgPack_Error_Success
EndProcedure
; Reads a boolean from the given data.
; Returns: The boolean value `#True` or `#False` as a `.b`
; OnError: Returns `#False` and sets the `MsgPackData\LastError` field.
; Will also raise a `DebuggerError`.
Procedure.b MsgPackReadBoolean(*MsgPackData.MsgPackData)
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData
@@ -58,7 +64,7 @@ Procedure.b MsgPackReadBoolean(*MsgPackData.MsgPackData)
*MsgPackData\LastError = #MsgPack_Error_Success
If _MsgPackIsAtTheEnd(*MsgPackData)
DebuggerError("End of buffer reached, cannot check data format code !")
Debug("End of buffer reached, cannot check data format code !")
*MsgPackData\LastError = #MsgPack_Error_AtOrPastTheEndOfBuffer
ProcedureReturn #False
@@ -79,10 +85,10 @@ Procedure.b MsgPackReadBoolean(*MsgPackData.MsgPackData)
ProcedureReturn #True
Else
DebuggerError("The format code isn't the one for a boolean ! (" +
RSet(Hex(PeekA(*MsgPackData\Buffer + *MsgPackData\BufferOffset)), 2, "0") +
" vs "+ RSet(Hex(#MsgPack_FormatCode_False), 2, "0") +
"/"+ RSet(Hex(#MsgPack_FormatCode_True), 2, "0") + ")")
Debug("The format code isn't the one for a boolean ! (" +
RSet(Hex(PeekA(*MsgPackData\Buffer + *MsgPackData\BufferOffset)), 2, "0") +
" vs "+ RSet(Hex(#MsgPack_FormatCode_False), 2, "0") +
"/"+ RSet(Hex(#MsgPack_FormatCode_True), 2, "0") + ")")
*MsgPackData\LastError = #MsgPack_Error_InvalidFormatCode
ProcedureReturn #False
@@ -102,8 +108,14 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteLiteral(*MsgPackData.MsgPackData, Liter
EndIf
CompilerEndIf
If _MsgPackHasFlag(*MsgPackData, #MsgPack_DataFlags_IsReadOnly)
Debug("Cannot write in a read-only buffer !")
*MsgPackData\LastError = #MsgPack_Error_BufferIsReadOnly
ProcedureReturn #MsgPack_Error_BufferIsReadOnly
EndIf
*MsgPackData\LastError = #MsgPack_Error_Success
If Not _MsgPackHasSpaceLeft(*MsgPackData, 1)
Protected GrowthErrorCode.MsgPack_ErrorCode
@@ -126,17 +138,6 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteLiteral(*MsgPackData.MsgPackData, Liter
EndProcedure
;-> > Utility
; Procedure.b MsgPackWriteBoolean(*MsgPackData.MsgPackData, Boolean)
; If Boolean
; ProcedureReturn MsgPackWriteTrue(*MsgPackData)
; Else
; ProcedureReturn MsgPackWriteFalse(*MsgPackData)
; EndIf
; EndProcedure
;-> Macro aliases
; @Function MsgPackWriteNull(*Ptr.MsgPackData).MsgPack_ErrorCode
@@ -155,3 +156,16 @@ Macro MsgPackWriteNull(MsgPackData) : _MsgPackWriteLiteral(MsgPackData, #MsgPack
Macro MsgPackWriteTrue(MsgPackData) : _MsgPackWriteLiteral(MsgPackData, #MsgPack_FormatCode_True) : EndMacro
Macro MsgPackWriteFalse(MsgPackData) : _MsgPackWriteLiteral(MsgPackData, #MsgPack_FormatCode_False) : EndMacro
;-> > Utility
; Writes `#True` or `#False` depending on `Boolean`.
; Returns `#MsgPack_Error_Success` on success, something else otherwise.
Procedure.MsgPack_ErrorCode MsgPackWriteBoolean(*MsgPackData.MsgPackData, Boolean.b)
If Boolean
ProcedureReturn MsgPackWriteTrue(*MsgPackData)
EndIf
ProcedureReturn MsgPackWriteFalse(*MsgPackData)
EndProcedure
+9 -2
View File
@@ -51,6 +51,12 @@ Procedure.MsgPack_ErrorCode _MsgPackReserveWrite(*MsgPackData.MsgPackData, Size.
; `Size` is a quad so sized fields up to 4 GiB can't be truncated, but the end
; offset must still fit in a `.i`, which isn't the case on x86 past 2 GiB.
If _MsgPackHasFlag(*MsgPackData, #MsgPack_DataFlags_IsReadOnly)
Debug("Cannot write in a read-only buffer !")
*MsgPackData\LastError = #MsgPack_Error_BufferIsReadOnly
ProcedureReturn #MsgPack_Error_BufferIsReadOnly
EndIf
If Size < 0 Or Size > #_MsgPack_MaxIntegerValue - *MsgPackData\BufferOffset
DebuggerWarning("Requested size can't be addressed: " + Str(Size))
*MsgPackData\LastError = #MsgPack_Error_BinaryDataSizeIsTooBig
@@ -143,6 +149,7 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteFixedField(*MsgPackData.MsgPackData, Fo
PokeQ(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, EndianSwapI64(Value))
EndSelect
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 1 + ValueByteLength
_MsgPackRewindOffsetAfterWrite(*MsgPackData, 1 + ValueByteLength)
ProcedureReturn #MsgPack_Error_Success
EndProcedure
@@ -169,7 +176,7 @@ Procedure.q _MsgPackReadFixedField(*MsgPackData.MsgPackData, FormatCode.MsgPack_
Case 8
ReturnedValue = EndianSwapI64(PeekQ(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1))
EndSelect
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 1 + ValueByteLength
_MsgPackMoveOffsetAfterRead(*MsgPackData, 1 + ValueByteLength)
ProcedureReturn ReturnedValue
EndProcedure
@@ -353,7 +360,7 @@ Procedure.q MsgPackReadInteger(*MsgPackData.MsgPackData)
#MsgPack_FormatCode_FixIntNegative:
; The whole byte is the value, both ranges are valid signed bytes.
ReturnedValue = PeekB(*MsgPackData\Buffer + *MsgPackData\BufferOffset)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 1
_MsgPackMoveOffsetAfterRead(*MsgPackData, 1)
*MsgPackData\LastError = #MsgPack_Error_Success
ProcedureReturn ReturnedValue
Case #MsgPack_FormatCode_UInt8:
+3 -2
View File
@@ -86,6 +86,7 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteStringField(*MsgPackData.MsgPackData, F
PokeS(*MsgPackData\Buffer + *MsgPackData\BufferOffset, String, -1, Encoding | #PB_String_NoZero)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + StringSize
EndIf
_MsgPackRewindOffsetAfterWrite(*MsgPackData, 1 + SizeFieldByteLength + StringSize)
ProcedureReturn #MsgPack_Error_Success
EndProcedure
@@ -128,7 +129,7 @@ Procedure.s MsgPackReadString(*MsgPackData.MsgPackData, Encoding.i = #PB_UTF8)
EndIf
If Encoding = #PB_Unicode And StringSize % 2 <> 0
DebuggerWarning("UTF-16 string has an odd byte size !")
Debug("UTF-16 string has an odd byte size !")
*MsgPackData\LastError = #MsgPack_Error_InvalidStringSize
ProcedureReturn ""
EndIf
@@ -146,7 +147,7 @@ Procedure.s MsgPackReadString(*MsgPackData.MsgPackData, Encoding.i = #PB_UTF8)
EndSelect
EndIf
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + HeaderSize + StringSize
_MsgPackMoveOffsetAfterRead(*MsgPackData, HeaderSize + StringSize)
ProcedureReturn ReturnedValue
EndProcedure
+6 -3
View File
@@ -42,7 +42,7 @@ Procedure.MsgPack_FormatCode MsgPackGetFormatCode(*MsgPackData.MsgPackData)
EndIf
If _MsgPackIsAtTheEnd(*MsgPackData)
DebuggerError("End of buffer reached, cannot check data format code !")
Debug("End of buffer reached, cannot check data format code !")
ProcedureReturn #MsgPack_FormatCode_Invalid
EndIf
@@ -131,9 +131,12 @@ Procedure.MsgPack_DataType MsgPackFormatCodeToDataType(FormatCode.MsgPack_Format
ProcedureReturn #MsgPack_DataTypes_Extension
Case #MsgPack_FormatCode_Invalid:
; Can come from the data itself, or `MsgPackGetFormatCode()` at the end of the buffer.
ProcedureReturn #MsgPack_DataTypes_Unknown
Default:
; FIXME: ADD MESSAGE HERE !!!
DebuggerWarning("")
; `MsgPackGetFormatCode()` never returns these, the caller passed an invalid value.
DebuggerWarning("Unknown format code: 0x" + RSet(Hex(FormatCode), 2, "0"))
ProcedureReturn #MsgPack_DataTypes_Unknown
EndSelect
EndProcedure
+117
View File
@@ -0,0 +1,117 @@
EnableExplicit
XIncludeFile "../_UnitTesting.pbi"
XIncludeFile "../../Includes/MessagePack.pbi"
XIncludeFile "../_TestBuffers.pbi"
; Checks that `#MsgPack_DataFlags_MoveOffsetAfterRead/Write` are honored by every family,
; and that `#MsgPack_DataFlags_IsReadOnly` blocks every write.
Define *MsgPack.MsgPackData
Define *Iterator.MsgPackData
Define *Output = AllocateMemory(16)
Define Bytes.l = $44332211
; Writes without moving the offset, each one overwriting the previous one
*MsgPack = MsgPackCreate(32)
*MsgPack\DataFlags = *MsgPack\DataFlags & (~#MsgPack_DataFlags_MoveOffsetAfterWrite)
AssertEquals(MsgPackWriteInteger(*MsgPack, 1000), #MsgPack_Error_Success, "Wrote an int without moving")
AssertEquals(*MsgPack\BufferOffset, 0, "Int write didn't move the offset")
AssertEquals(MsgPackWriteString(*MsgPack, "abc"), #MsgPack_Error_Success, "Wrote a string without moving")
AssertEquals(*MsgPack\BufferOffset, 0, "String write didn't move the offset")
AssertEquals(MsgPackWriteBinary(*MsgPack, @Bytes, 4), #MsgPack_Error_Success, "Wrote a bin without moving")
AssertEquals(*MsgPack\BufferOffset, 0, "Bin write didn't move the offset")
AssertEquals(MsgPackWriteExtension(*MsgPack, 1, @Bytes, 4), #MsgPack_Error_Success, "Wrote a fixext without moving")
AssertEquals(*MsgPack\BufferOffset, 0, "Fixext write didn't move the offset")
AssertEquals(MsgPackWriteExt8(*MsgPack, 1, @Bytes, 3), #MsgPack_Error_Success, "Wrote an ext8 without moving")
AssertEquals(*MsgPack\BufferOffset, 0, "Ext8 write didn't move the offset")
AssertEquals(MsgPackWriteArray(*MsgPack, @Bytes, 2), #MsgPack_Error_Success, "Wrote an array without moving")
AssertEquals(*MsgPack\BufferOffset, 0, "Array write didn't move the offset")
AssertEqualsA(PeekA(*MsgPack\Buffer), $92, "Array was written at the start")
MsgPackFree(*MsgPack, #True)
; Reads without moving the offset, so the same field can be read twice
*MsgPack = WrapHex("CD03E8A3616263C4021122D401AAC0")
*MsgPack\DataFlags = *MsgPack\DataFlags & (~#MsgPack_DataFlags_MoveOffsetAfterRead)
AssertEqualsQ(MsgPackReadInteger(*MsgPack), 1000, "Peeked an int")
AssertEqualsQ(MsgPackReadUInt16(*MsgPack), 1000, "Read the same int again")
AssertEquals(*MsgPack\BufferOffset, 0, "Int reads didn't move the offset")
MsgPackSkip(*MsgPack)
AssertIsTrue(Bool(MsgPackReadString(*MsgPack) = "abc"), "Peeked a string")
AssertEquals(*MsgPack\BufferOffset, 3, "String read didn't move the offset")
MsgPackSkip(*MsgPack)
AssertEqualsQ(MsgPackReadBinary(*MsgPack, *Output, 16), 2, "Peeked a bin")
AssertEquals(*MsgPack\BufferOffset, 7, "Bin read didn't move the offset")
MsgPackSkip(*MsgPack)
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 16), 1, "Peeked an ext")
AssertEquals(*MsgPack\BufferOffset, 11, "Ext read didn't move the offset")
MsgPackSkip(*MsgPack)
AssertEquals(MsgPackReadNull(*MsgPack), #MsgPack_Error_Success, "Peeked a nil")
AssertEquals(*MsgPack\BufferOffset, 14, "Nil read didn't move the offset")
FreeWrappedHex(*MsgPack)
; Views are read-only: [1, 2]
*MsgPack = WrapHex("920102")
*Iterator = MsgPackExamine(*MsgPack)
MsgPackNextEntry(*Iterator)
AssertEquals(MsgPackWriteNull(*Iterator), #MsgPack_Error_BufferIsReadOnly, "Cannot write a literal in a view")
AssertEquals(MsgPackWriteBoolean(*Iterator, #True), #MsgPack_Error_BufferIsReadOnly, "Cannot write a boolean in a view")
AssertEquals(MsgPackWriteInteger(*Iterator, 5), #MsgPack_Error_BufferIsReadOnly, "Cannot write an int in a view")
AssertEquals(MsgPackWriteString(*Iterator, "a"), #MsgPack_Error_BufferIsReadOnly, "Cannot write a string in a view")
AssertEquals(MsgPackWriteBinary(*Iterator, @Bytes, 1), #MsgPack_Error_BufferIsReadOnly, "Cannot write a bin in a view")
AssertEquals(MsgPackWriteExtension(*Iterator, 1, @Bytes, 1), #MsgPack_Error_BufferIsReadOnly, "Cannot write an ext in a view")
AssertEquals(MsgPackWriteArray(*Iterator, @Bytes, 1), #MsgPack_Error_BufferIsReadOnly, "Cannot write an array in a view")
AssertEquals(MsgPackLastError(*Iterator), #MsgPack_Error_BufferIsReadOnly, "Read-only error is set")
AssertEquals(*Iterator\BufferOffset, 0, "Offset is untouched by the rejected writes")
AssertIsTrue(Bool(PeekA(*MsgPack\Buffer + 1) = $01 And PeekA(*MsgPack\Buffer + 2) = $02), "Parent data is untouched")
AssertEqualsQ(MsgPackReadInteger(*Iterator), 1, "View can still be read")
MsgPackFinish(*Iterator)
FreeWrappedHex(*MsgPack)
; Nil and booleans
*MsgPack = MsgPackCreate(1)
AssertEquals(MsgPackWriteBoolean(*MsgPack, #True), #MsgPack_Error_Success, "Wrote a true boolean")
AssertEquals(MsgPackWriteBoolean(*MsgPack, #False), #MsgPack_Error_Success, "Wrote a false boolean")
AssertEquals(MsgPackWriteNull(*MsgPack), #MsgPack_Error_Success, "Wrote a nil")
*MsgPack\BufferOffset = 0
AssertEquals(MsgPackReadNull(*MsgPack), #MsgPack_Error_InvalidFormatCode, "True isn't a nil")
AssertEquals(*MsgPack\BufferOffset, 0, "Failed nil read didn't move the offset")
AssertIsTrue(MsgPackReadBoolean(*MsgPack), "Read the true boolean")
AssertIsFalse(MsgPackReadBoolean(*MsgPack), "Read the false boolean")
AssertIsFalse(MsgPackReadBoolean(*MsgPack), "Nil isn't a boolean")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "Boolean error is set")
AssertEquals(MsgPackReadNull(*MsgPack), #MsgPack_Error_Success, "Read the nil")
AssertEquals(MsgPackReadNull(*MsgPack), #MsgPack_Error_AtOrPastTheEndOfBuffer, "Cannot read a nil at the end")
AssertEquals(MsgPackGetFormatCode(*MsgPack), #MsgPack_FormatCode_Invalid, "No format code at the end")
AssertEquals(MsgPackFormatCodeToDataType(MsgPackGetFormatCode(*MsgPack)), #MsgPack_DataTypes_Unknown, "No data type at the end")
MsgPackFree(*MsgPack, #True)
FreeMemory(*Output)
If FailedUnitTests
End 1
EndIf
Debug "Done !"
End 0
@@ -28,9 +28,9 @@ Procedure TestValueMetaInfoWithin(CodeForBuffer.a, ExpectedVariableSize.q, Expec
EndProcedure
; Positive FixInt
TestValueMetaInfoWithin($00, 0, 1, "FixInt with positive 0") ; Value: 0 (0x00) | Offset: ???
TestValueMetaInfoWithin($7F, 127, 0, "FixInt with positive 127") ; Value: 127 (0x7F) | Offset: ???
; Positive FixInt, unsupported since `MsgPackReadInteger()` handles them
TestValueMetaInfoWithin($00, #MsgPack_InvalidSize, #MsgPack_InvalidSize, "FixInt with positive 0")
TestValueMetaInfoWithin($7F, #MsgPack_InvalidSize, #MsgPack_InvalidSize, "FixInt with positive 127")
; FixMap
TestValueMetaInfoWithin($80, 0, 1, "FixMap with 0 elements") ; Value: 0 (0x0) | Offset: 1
@@ -46,9 +46,9 @@ TestValueMetaInfoWithin($AF, 15, 1, "FixStr with 15 elements") ; Value: 15 (0x0F
TestValueMetaInfoWithin($B0, 16, 1, "FixStr with 16 elements") ; Value: 16 (0x10) | Offset: 1
TestValueMetaInfoWithin($BF, 31, 1, "FixStr with 31 elements") ; Value: 31 (0x1F) | Offset: 1
; Negative FixInt
TestValueMetaInfoWithin($E0, -0, 1, "FixInt with negative 0") ; Value: 0 (0x00) | Offset: ???
TestValueMetaInfoWithin($FF, -31, 0, "FixInt with negative 31") ; Value: 31 (0x7F) | Offset: ???
; Negative FixInt, unsupported since `MsgPackReadInteger()` handles them
TestValueMetaInfoWithin($E0, #MsgPack_InvalidSize, #MsgPack_InvalidSize, "FixInt with negative 32")
TestValueMetaInfoWithin($FF, #MsgPack_InvalidSize, #MsgPack_InvalidSize, "FixInt with negative 1")
@@ -0,0 +1,146 @@
EnableExplicit
XIncludeFile "../_UnitTesting.pbi"
XIncludeFile "../../Includes/MessagePack.pbi"
XIncludeFile "../_TestBuffers.pbi"
; Note: Only covers errors that don't call `DebuggerError()`.
Procedure.s WrittenHex(*MsgPack.MsgPackData)
Protected Result.s = ""
Protected i.i
For i = 0 To *MsgPack\BufferOffset - 1
Result = Result + RSet(Hex(PeekA(*MsgPack\Buffer + i)), 2, "0")
Next
ProcedureReturn Result
EndProcedure
; Writes the content given as hex with `WriteMode` and checks the whole output.
; Modes: 0 = Array, 1 = Map, 2 = FixArray, 3 = Array16, 4 = Array32, 5 = FixMap, 6 = Map16, 7 = Map32
Procedure TestWrite(WriteMode.i, ContentHex.s, ExpectedError.i, ExpectedHex.s, TestName.s)
Protected *Content.MsgPackData = WrapHex(ContentHex + "00")
Protected *MsgPack.MsgPackData = MsgPackCreate(1)
Protected ContentSize.q = Len(ContentHex) / 2
Protected ErrorCode.i
Select WriteMode
Case 0 : ErrorCode = MsgPackWriteArray(*MsgPack, *Content\Buffer, ContentSize)
Case 1 : ErrorCode = MsgPackWriteMap(*MsgPack, *Content\Buffer, ContentSize)
Case 2 : ErrorCode = MsgPackWriteFixArray(*MsgPack, *Content\Buffer, ContentSize)
Case 3 : ErrorCode = MsgPackWriteArray16(*MsgPack, *Content\Buffer, ContentSize)
Case 4 : ErrorCode = MsgPackWriteArray32(*MsgPack, *Content\Buffer, ContentSize)
Case 5 : ErrorCode = MsgPackWriteFixMap(*MsgPack, *Content\Buffer, ContentSize)
Case 6 : ErrorCode = MsgPackWriteMap16(*MsgPack, *Content\Buffer, ContentSize)
Case 7 : ErrorCode = MsgPackWriteMap32(*MsgPack, *Content\Buffer, ContentSize)
EndSelect
AssertEquals(ErrorCode, ExpectedError, TestName + " (Error)")
AssertEquals(MsgPackLastError(*MsgPack), ExpectedError, TestName + " (Last error)")
AssertIsTrue(Bool(WrittenHex(*MsgPack) = ExpectedHex), TestName + " (Output)", WrittenHex(*MsgPack) + " <> " + ExpectedHex)
MsgPackFree(*MsgPack, #True)
FreeWrappedHex(*Content)
EndProcedure
Define *MsgPack.MsgPackData
Define *Inner.MsgPackData
Define *Iterator.MsgPackData
Define *Finalized
Define Content.s
Define i.i
; Smallest fit
TestWrite(0, "", #MsgPack_Error_Success, "90", "Empty array")
TestWrite(1, "", #MsgPack_Error_Success, "80", "Empty map")
TestWrite(0, "01A16192C0C3", #MsgPack_Error_Success, "9301A16192C0C3", "FixArray with a nested array")
TestWrite(1, "A16101A16292C0C3", #MsgPack_Error_Success, "82A16101A16292C0C3", "FixMap with a nested array")
Content = ""
For i = 1 To 16
Content = Content + "C0"
Next
TestWrite(0, Content, #MsgPack_Error_Success, "DC0010" + Content, "Array16 for 16 entries")
TestWrite(1, Content, #MsgPack_Error_Success, "88" + Content, "FixMap for 8 pairs")
Content = ""
For i = 1 To 32
Content = Content + "C0"
Next
TestWrite(1, Content, #MsgPack_Error_Success, "DE0010" + Content, "Map16 for 16 pairs")
; Forced format codes
TestWrite(2, "01", #MsgPack_Error_Success, "9101", "Forced FixArray")
TestWrite(3, "01", #MsgPack_Error_Success, "DC000101", "Forced Array16")
TestWrite(4, "01", #MsgPack_Error_Success, "DD0000000101", "Forced Array32")
TestWrite(5, "0102", #MsgPack_Error_Success, "810102", "Forced FixMap")
TestWrite(6, "0102", #MsgPack_Error_Success, "DE00010102", "Forced Map16")
TestWrite(7, "0102", #MsgPack_Error_Success, "DF000000010102", "Forced Map32")
; Errors, nothing must be written
TestWrite(1, "A16101A162", #MsgPack_Error_OddMapFieldCount, "", "Map with a key lacking its value")
TestWrite(5, "01", #MsgPack_Error_OddMapFieldCount, "", "FixMap with a single field")
TestWrite(2, Content, #MsgPack_Error_TooManyEntries, "", "FixArray with 32 entries")
TestWrite(0, "01C1", #MsgPack_Error_InvalidFormatCode, "", "Array with invalid content")
TestWrite(0, "01A361", #MsgPack_Error_BufferTooSmall, "", "Array with a truncated field")
TestWrite(0, "9201", #MsgPack_Error_BufferTooSmall, "", "Array with a truncated nested array")
; From a `MsgPackData`, and from a finalized buffer, then read back
*Inner = MsgPackCreate(4)
MsgPackWriteString(*Inner, "id")
MsgPackWriteInteger(*Inner, 1234)
MsgPackWriteString(*Inner, "tags")
*MsgPack = MsgPackCreate(1)
AssertEquals(MsgPackWriteMapFrom(*MsgPack, *Inner), #MsgPack_Error_OddMapFieldCount, "Inner map content lacks a value")
AssertEquals(*MsgPack\BufferOffset, 0, "Nothing was written for the odd map")
MsgPackWriteArray(*Inner, ?Tags, ?TagsEnd - ?Tags)
AssertEquals(MsgPackWriteMapFrom(*MsgPack, *Inner), #MsgPack_Error_Success, "Wrote the map from a MsgPackData")
AssertEquals(MsgPackWriteArrayFrom(*MsgPack, *Inner), #MsgPack_Error_Success, "Wrote an array from the same MsgPackData")
*Finalized = MsgPackFinalize(*Inner)
AssertEquals(MsgPackWriteArray(*MsgPack, *Finalized, MemorySize(*Finalized)), #MsgPack_Error_Success, "Wrote an array from a finalized buffer")
FreeMemory(*Finalized)
AssertIsTrue(Bool(WrittenHex(*MsgPack) = "82A26964CD04D2A47461677392A161A162" + "94A26964CD04D2A47461677392A161A162" + "94A26964CD04D2A47461677392A161A162"), "Written buffer", WrittenHex(*MsgPack))
*MsgPack\BufferOffset = 0
*Iterator = MsgPackExamine(*MsgPack)
AssertIsTrue(MsgPackNextEntry(*Iterator), "Read back entry #1")
AssertIsTrue(Bool(MsgPackReadString(*Iterator) = "id"), "Read back key #1")
AssertEqualsQ(MsgPackReadInteger(*Iterator), 1234, "Read back value #1")
AssertIsTrue(MsgPackNextEntry(*Iterator), "Read back entry #2")
AssertIsTrue(Bool(MsgPackReadString(*Iterator) = "tags"), "Read back key #2")
AssertEqualsQ(MsgPackGetEntryCount(*Iterator), 2, "Read back value #2 entry count")
AssertIsFalse(MsgPackNextEntry(*Iterator), "Read back map has 2 entries")
MsgPackFinish(*Iterator)
AssertEqualsQ(MsgPackGetEntryCount(*MsgPack), 4, "Read back array has 4 entries")
MsgPackFree(*MsgPack, #True)
If FailedUnitTests
End 1
EndIf
Debug "Done !"
End 0
DataSection
Tags:
Data.a $A1, $61, $A1, $62
TagsEnd:
EndDataSection
+253
View File
@@ -0,0 +1,253 @@
EnableExplicit
XIncludeFile "../_UnitTesting.pbi"
XIncludeFile "../../Includes/MessagePack.pbi"
XIncludeFile "../_TestBuffers.pbi"
; Note: Only covers errors that don't call `DebuggerError()`.
; Setup
Define *MsgPack.MsgPackData
Define *Iterator.MsgPackData
Define *Nested.MsgPackData
Define EntryCount.i
Define i.i
Define Hex.s
Procedure TestFullSize(Hex.s, ExpectedSize.q, TestName.s)
Protected *MsgPack.MsgPackData = WrapHex(Hex)
AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), ExpectedSize, TestName + " (Size)")
AssertEquals(*MsgPack\BufferOffset, 0, TestName + " (Offset untouched)")
FreeWrappedHex(*MsgPack)
EndProcedure
; Full sizes of single fields, with an extra trailing byte that must not be counted
TestFullSize("C0FF", 1, "Nil")
TestFullSize("C3FF", 1, "True")
TestFullSize("7FFF", 1, "Positive fixint")
TestFullSize("E0FF", 1, "Negative fixint")
TestFullSize("CCFFFF", 2, "UInt8")
TestFullSize("D10001FF", 3, "Int16")
TestFullSize("CE00000001FF", 5, "UInt32")
TestFullSize("CA3FC00000FF", 5, "Float32")
TestFullSize("D30000000000000001FF", 9, "Int64")
TestFullSize("CB4000000000000000FF", 9, "Float64")
TestFullSize("A26162FF", 3, "FixStr")
TestFullSize("D9026162FF", 4, "Str8")
TestFullSize("C50002AAAAFF", 5, "Bin16")
TestFullSize("D401AAFF", 3, "FixExt1")
TestFullSize("D801AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFF", 18, "FixExt16")
TestFullSize("C70205AAAAFF", 5, "Ext8")
TestFullSize("90FF", 1, "Empty array")
TestFullSize("80FF", 1, "Empty map")
TestFullSize("920102FF", 3, "FixArray")
TestFullSize("DC00020102FF", 5, "Array16")
TestFullSize("DD000000020102FF", 7, "Array32")
TestFullSize("82A16101A16202FF", 7, "FixMap")
TestFullSize("DE0001A16101FF", 6, "Map16")
TestFullSize("9291C091C0FF", 5, "Nested arrays")
; Invalid and truncated fields
*MsgPack = WrapHex("C1")
AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), #MsgPack_InvalidSize, "Invalid format code has no size")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "Invalid format code error is set")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("930102")
AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), #MsgPack_InvalidSize, "Truncated array has no size")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Truncated array error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the truncated array")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("DDFFFFFFFF01")
AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), #MsgPack_InvalidSize, "Bogus array size is rejected")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Bogus array size error is set")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("CD00")
AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), #MsgPack_InvalidSize, "Truncated UInt16 has no size")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Truncated UInt16 error is set")
FreeWrappedHex(*MsgPack)
; Deep nesting doesn't overflow the stack
Hex = ""
For i = 1 To 10000
Hex = Hex + "91"
Next
*MsgPack = WrapHex(Hex + "01")
AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), 10001, "Deeply nested arrays")
FreeWrappedHex(*MsgPack)
; Entry counts
*MsgPack = WrapHex("DC0003010203")
AssertEqualsQ(MsgPackGetEntryCount(*MsgPack), 3, "Array16 entry count")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("82A16101A16202")
AssertEqualsQ(MsgPackGetEntryCount(*MsgPack), 2, "Map entry count")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("01")
AssertEqualsQ(MsgPackGetEntryCount(*MsgPack), #MsgPack_InvalidSize, "Fixint has no entry count")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "Entry count error is set")
FreeWrappedHex(*MsgPack)
; Skipping
*MsgPack = WrapHex("01A161C0")
AssertEquals(MsgPackSkip(*MsgPack, 2), #MsgPack_Error_Success, "Skipped 2 fields")
AssertEquals(*MsgPack\BufferOffset, 3, "Offset after skipping 2 fields")
AssertEquals(MsgPackSkip(*MsgPack, 2), #MsgPack_Error_BufferTooSmall, "Cannot skip past the end")
AssertEquals(*MsgPack\BufferOffset, 3, "Offset is untouched by the failed skip")
AssertEquals(MsgPackSkip(*MsgPack), #MsgPack_Error_Success, "Skipped the last field")
AssertIsTrue(Bool(_MsgPackIsAtTheEnd(*MsgPack)), "Reached the end by skipping")
AssertEquals(MsgPackSkip(*MsgPack), #MsgPack_Error_AtOrPastTheEndOfBuffer, "Cannot skip at the end")
FreeWrappedHex(*MsgPack)
; Iterating on an array: [1, "ab", [true, nil], {"k": [1, 2]}, 3] followed by `false`
*MsgPack = WrapHex("9501A26162" + "92C3C0" + "81A16B920102" + "03" + "C2")
*Iterator = MsgPackExamine(*MsgPack)
AssertIsTrue(Bool(*Iterator <> #Null), "Examined the array")
AssertEquals(*MsgPack\BufferOffset, 15, "Offset was moved past the array")
AssertEquals(*Iterator\BufferSize, 14, "View is limited to the array's content")
EntryCount = 0
While MsgPackNextEntry(*Iterator)
EntryCount = EntryCount + 1
Select EntryCount
Case 1
AssertEqualsQ(MsgPackReadInteger(*Iterator), 1, "Entry #1")
Case 2
AssertIsTrue(Bool(MsgPackReadString(*Iterator) = "ab"), "Entry #2")
Case 3
; Left unread, must be skipped as a whole
AssertEquals(MsgPackFormatCodeToDataType(MsgPackGetFormatCode(*Iterator)), #MsgPack_DataTypes_Array, "Entry #3 is an array")
Case 4
*Nested = MsgPackExamine(*Iterator)
AssertIsTrue(Bool(*Nested <> #Null), "Examined the nested map")
AssertIsTrue(MsgPackNextEntry(*Nested), "Nested map has an entry")
AssertIsTrue(Bool(MsgPackReadString(*Nested) = "k"), "Nested map key")
AssertEqualsQ(MsgPackGetEntryCount(*Nested), 2, "Nested map value is a 2 entries array")
AssertIsFalse(MsgPackNextEntry(*Nested), "Nested map has a single entry")
AssertEquals(MsgPackLastError(*Nested), #MsgPack_Error_Success, "Nested map ended without errors")
MsgPackFinish(*Nested)
Case 5
AssertEqualsQ(MsgPackReadInteger(*Iterator), 3, "Entry #5")
EndSelect
Wend
AssertEquals(EntryCount, 5, "Iterated on 5 entries")
AssertEquals(MsgPackLastError(*Iterator), #MsgPack_Error_Success, "Array ended without errors")
AssertIsFalse(MsgPackNextEntry(*Iterator), "Iteration stays over")
MsgPackFinish(*Iterator)
AssertIsFalse(MsgPackReadBoolean(*MsgPack), "Read the field following the array")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "Field following the array was read properly")
FreeWrappedHex(*MsgPack)
; Iterating on a map while only reading the keys: {"a": 1, "b": [2, 3]}
*MsgPack = WrapHex("82A16101A16292020305")
*Iterator = MsgPackExamine(*MsgPack)
AssertIsTrue(MsgPackNextEntry(*Iterator), "Map entry #1")
AssertIsTrue(Bool(MsgPackReadString(*Iterator) = "a"), "Map key #1")
AssertIsTrue(MsgPackNextEntry(*Iterator), "Map entry #2")
AssertIsTrue(Bool(MsgPackReadString(*Iterator) = "b"), "Map key #2")
AssertEquals(MsgPackSkip(*Iterator), #MsgPack_Error_Success, "Skipped map value #2")
AssertEqualsQ(MsgPackReadInteger(*Iterator), 0, "Cannot read past the map's end")
AssertEquals(MsgPackLastError(*Iterator), #MsgPack_Error_AtOrPastTheEndOfBuffer, "Map's end error is set")
AssertIsFalse(MsgPackNextEntry(*Iterator), "Map has 2 entries")
MsgPackFinish(*Iterator)
AssertEqualsQ(MsgPackReadInteger(*MsgPack), 5, "Read the field following the map")
FreeWrappedHex(*MsgPack)
; Empty arrays and maps
*MsgPack = WrapHex("9080")
*Iterator = MsgPackExamine(*MsgPack)
AssertIsTrue(Bool(*Iterator <> #Null), "Examined the empty array")
AssertIsFalse(MsgPackNextEntry(*Iterator), "Empty array has no entries")
MsgPackFinish(*Iterator)
*Iterator = MsgPackExamine(*MsgPack)
AssertIsTrue(Bool(*Iterator <> #Null), "Examined the empty map")
AssertIsFalse(MsgPackNextEntry(*Iterator), "Empty map has no entries")
MsgPackFinish(*Iterator)
FreeWrappedHex(*MsgPack)
; Examination errors
*MsgPack = WrapHex("01")
AssertIsTrue(Bool(MsgPackExamine(*MsgPack) = #Null), "Cannot examine a fixint")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "Examination error is set")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("920191")
AssertIsTrue(Bool(MsgPackExamine(*MsgPack) = #Null), "Cannot examine a truncated array")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Truncated array error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the failed examination")
FreeWrappedHex(*MsgPack)
; Unsafe examination: {"a": [1], "b": 2} followed by `5`
*MsgPack = WrapHex("82A1619101A1620205")
*Iterator = MsgPackExamine(*MsgPack, #MsgPack_ExaminationOptions_Unsafe)
AssertIsTrue(Bool(*Iterator <> #Null), "Examined the map unsafely")
AssertEquals(*MsgPack\BufferOffset, 0, "Unsafe examination doesn't move the offset")
AssertEquals(*Iterator\BufferSize, 8, "Unsafe view goes up to the end of the buffer")
EntryCount = 0
While MsgPackNextEntry(*Iterator)
EntryCount = EntryCount + 1
If EntryCount = 2
AssertIsTrue(Bool(MsgPackReadString(*Iterator) = "b"), "Unsafe map key #2")
AssertEqualsQ(MsgPackReadInteger(*Iterator), 2, "Unsafe map value #2")
EndIf
Wend
AssertEquals(EntryCount, 2, "Unsafe map has 2 entries")
AssertEquals(MsgPackLastError(*Iterator), #MsgPack_Error_Success, "Unsafe map ended without errors")
AssertEquals(*Iterator\BufferSize, 7, "Unsafe view is limited once its end is reached")
AssertEqualsQ(MsgPackReadInteger(*Iterator), 0, "Cannot read past the unsafe map's end")
AssertEquals(MsgPackLastError(*Iterator), #MsgPack_Error_AtOrPastTheEndOfBuffer, "Unsafe map's end error is set")
MsgPackFinish(*Iterator)
AssertEquals(MsgPackSkip(*MsgPack), #MsgPack_Error_Success, "Skipped the unsafely examined map")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), 5, "Read the field following the unsafe map")
FreeWrappedHex(*MsgPack)
; Invalid content is only detected while iterating
*MsgPack = WrapHex("9201C1")
*Iterator = MsgPackExamine(*MsgPack, #MsgPack_ExaminationOptions_Unsafe)
AssertIsTrue(Bool(*Iterator <> #Null), "Examined the invalid array unsafely")
AssertIsTrue(MsgPackNextEntry(*Iterator), "Invalid array entry #1")
AssertIsTrue(MsgPackNextEntry(*Iterator), "Invalid array entry #2 is reached")
AssertIsFalse(MsgPackNextEntry(*Iterator), "Cannot skip the invalid entry #2")
AssertEquals(MsgPackLastError(*Iterator), #MsgPack_Error_InvalidFormatCode, "Invalid entry error is set")
MsgPackFinish(*Iterator)
FreeWrappedHex(*MsgPack)
If FailedUnitTests
End 1
EndIf
Debug "Done !"
End 0